├── .github
└── ISSUE_TEMPLATE
│ ├── -------issue---.md
│ ├── bug-----issue---.md
│ └── bug-report--english-issue-template-.md
├── .gitignore
├── LICENSE
├── MANIFEST.in
├── README.md
├── p.bat
├── setup.py
├── up.bat
└── vogen
├── .idea
├── .gitignore
├── inspectionProfiles
│ └── profiles_settings.xml
├── misc.xml
├── modules.xml
└── vogen.iml
├── __init__.py
├── __main__.py
├── config
├── __init__.py
└── config.json
├── pm.py
└── synth
├── __init__.py
├── acoustics.py
├── f0.py
├── g2p.py
├── libs
└── .gitkeep
├── models
├── f0.man.onnx
├── f0.yue.onnx
├── g2p.man.onnx
├── g2p.yue-wz.onnx
├── g2p.yue.onnx
├── po.man.onnx
├── po.yue.onnx
└── rfft.onnx
├── prosody.py
├── timetable.py
└── utils.py
/.github/ISSUE_TEMPLATE/-------issue---.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: 功能请求(中文issue模板)
3 | about: 你希望我们实现哪些功能?
4 | title: "[Feature]"
5 | labels: enhancement
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## 想要实现的功能
11 |
12 | ## 可能的实现手段(可选)
13 | (这个功能可以通过什么方式实现?提出你的想法)
14 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-----issue---.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug反馈(中文issue模板)
3 | about: 报告PyVogen库的Bug
4 | title: "[Bug]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Bug描述
11 | (完整的报错信息,异常的输出)
12 |
13 | ## 如何复现
14 | (产生Bug的**完整**代码)
15 |
16 | ## 环境信息
17 | (Python版本,PyVogen版本,操作系统版本,是否为jupyter notebook或Colab)
18 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug-report--english-issue-template-.md:
--------------------------------------------------------------------------------
1 | ---
2 | name: Bug Report (English issue template)
3 | about: Report bugs related to PyVogen library
4 | title: "[Bug]"
5 | labels: bug
6 | assignees: ''
7 |
8 | ---
9 |
10 | ## Bug Description
11 | (Full python error message, or unexpected output produced by PyVogen)
12 |
13 | ## How to reproduce
14 | (Full python code to reproduce this bug)
15 |
16 | ## Environment
17 | (Python version, PyVogen version, OS version, Are you using jupyter notebook or Colab?)
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | #我的
2 | vogen/config/config.json
3 | vogen/synth/libs/
4 | *.bat
5 |
6 | # Byte-compiled / optimized / DLL files
7 | __pycache__/
8 | *.py[cod]
9 | *$py.class
10 |
11 | # C extensions
12 | *.so
13 |
14 | # Distribution / packaging
15 | .Python
16 | build/
17 | develop-eggs/
18 | dist/
19 | downloads/
20 | eggs/
21 | .eggs/
22 | lib/
23 | lib64/
24 | parts/
25 | sdist/
26 | var/
27 | wheels/
28 | pip-wheel-metadata/
29 | share/python-wheels/
30 | *.egg-info/
31 | .installed.cfg
32 | *.egg
33 | MANIFEST
34 |
35 | # PyInstaller
36 | # Usually these files are written by a python script from a template
37 | # before PyInstaller builds the exe, so as to inject date/other infos into it.
38 | *.manifest
39 | *.spec
40 |
41 | # Installer logs
42 | pip-log.txt
43 | pip-delete-this-directory.txt
44 |
45 | # Unit test / coverage reports
46 | htmlcov/
47 | .tox/
48 | .nox/
49 | .coverage
50 | .coverage.*
51 | .cache
52 | nosetests.xml
53 | coverage.xml
54 | *.cover
55 | *.py,cover
56 | .hypothesis/
57 | .pytest_cache/
58 |
59 | # Translations
60 | *.mo
61 | *.pot
62 |
63 | # Django stuff:
64 | *.log
65 | local_settings.py
66 | db.sqlite3
67 | db.sqlite3-journal
68 |
69 | # Flask stuff:
70 | instance/
71 | .webassets-cache
72 |
73 | # Scrapy stuff:
74 | .scrapy
75 |
76 | # Sphinx documentation
77 | docs/_build/
78 |
79 | # PyBuilder
80 | target/
81 |
82 | # Jupyter Notebook
83 | .ipynb_checkpoints
84 |
85 | # IPython
86 | profile_default/
87 | ipython_config.py
88 |
89 | # pyenv
90 | .python-version
91 |
92 | # pipenv
93 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
94 | # However, in case of collaboration, if having platform-specific dependencies or dependencies
95 | # having no cross-platform support, pipenv may install dependencies that don't work, or not
96 | # install all needed dependencies.
97 | #Pipfile.lock
98 |
99 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow
100 | __pypackages__/
101 |
102 | # Celery stuff
103 | celerybeat-schedule
104 | celerybeat.pid
105 |
106 | # SageMath parsed files
107 | *.sage.py
108 |
109 | # Environments
110 | .env
111 | .venv
112 | env/
113 | venv/
114 | ENV/
115 | env.bak/
116 | venv.bak/
117 |
118 | # Spyder project settings
119 | .spyderproject
120 | .spyproject
121 |
122 | # Rope project settings
123 | .ropeproject
124 |
125 | # mkdocs documentation
126 | /site
127 |
128 | # mypy
129 | .mypy_cache/
130 | .dmypy.json
131 | dmypy.json
132 |
133 | # Pyre type checker
134 | .pyre/
135 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 oxygen-dioxide
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/MANIFEST.in:
--------------------------------------------------------------------------------
1 | recursive-include vogen/synth/models *.onnx
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PyVogen
2 | [Github](https://github.com/oxygen-dioxide/vogen) |
3 | [Gitee](https://gitee.com/oxygendioxide/vogen) |
4 | [Gitlab](https://gitlab.com/oxygen-dioxide/vogen) |
5 | [Bitbucket](https://bitbucket.org/oxygendioxide/vogen) |
6 | [Coding](https://oxygen-dioxide.coding.net/public/1/vogen/git/files)
7 |
8 | PyVogen是开源歌声合成引擎[Vogen](https://github.com/aqtq314/Vogen.Client)的python实现
9 |
10 | 本python库依赖以下库:
11 | [wget](http://bitbucket.org/techtonik/python-wget/)
12 | [tqdm](https://tqdm.github.io/)
13 | [numpy](https://numpy.org/)
14 | [pyworld](https://github.com/JeremyCCHsu/Python-Wrapper-for-World-Vocoder)
15 | [jyutping](https://github.com/imdreamrunner/python-jyutping)
16 | [tabulate](https://github.com/astanin/python-tabulate)
17 | [pypinyin](https://pypinyin.readthedocs.io/zh_CN/master/)
18 | [pyperclip](https://github.com/asweigart/pyperclip)
19 | [onnxruntime](https://www.onnxruntime.ai/)
20 | [simpleaudio](https://github.com/hamiltron/py-simple-audio)
21 | [more-itertools](https://more-itertools.readthedocs.io/)
22 |
23 | ## 安装
24 | ```
25 | pip install vogen
26 | ```
27 |
28 | ## 示例
29 |
30 | 以下是文件读写示例,音频合成等更多示例参见[文档](https://github.com/oxygen-dioxide/pyvogen-docs)
31 |
32 | ```py
33 | import vogen as vg
34 |
35 | #打开文件
36 | vf=vg.openvog("myproject.vog")
37 |
38 | # 获取第0乐句的歌词与拼音列表
39 | u=vf.utts[0]
40 | lyrics=[i.lyric for i in u.notes]
41 | roms=[i.rom for i in u.notes]
42 | print(lyrics)
43 | print(roms)
44 |
45 | #保存文件
46 | vf.save("myproject2.vog")
47 | ```
48 |
49 | # 相关链接
50 | [vogen仓库](https://github.com/aqtq314/Vogen.Client)
51 |
52 | [vogen作者的b站空间(vogen试听页面)](https://space.bilibili.com/169955)
--------------------------------------------------------------------------------
/p.bat:
--------------------------------------------------------------------------------
1 | git push gitee
2 | git push origin
3 | git push bitbucket
4 | git push coding
--------------------------------------------------------------------------------
/setup.py:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env python
2 | # -*- coding: utf-8 -*-
3 |
4 | #Based on https://github.com/navdeep-G/setup.py
5 |
6 | # Note: To use the 'upload' functionality of this file, you must:
7 | # $ pipenv install twine --dev
8 |
9 | import io
10 | import os
11 | import sys
12 | from shutil import rmtree
13 |
14 | from setuptools import find_packages, setup, Command
15 |
16 | # Package meta-data.
17 | NAME = 'vogen'
18 | DESCRIPTION = 'An AI based vocal synthesizer implemented in python and onnx, with a music21 interface'
19 | URL = 'https://github.com/oxygen-dioxide/vogen'
20 | EMAIL = '1463567152@qq.com'
21 | AUTHOR = 'oxygen dioxide'
22 | REQUIRES_PYTHON = '>=3.5.0'
23 | VERSION = '0.0.7'
24 |
25 | REQUIRED = ["wget",
26 | "tqdm",
27 | "numpy",
28 | "pyworld",
29 | "jyutping",
30 | "pypinyin",
31 | "tabulate",
32 | "pyperclip",
33 | "onnxruntime",
34 | "simpleaudio",
35 | "more-itertools",
36 | ]
37 | EXTRAS = {}
38 |
39 | # The rest you shouldn't have to touch too much :)
40 | # ------------------------------------------------
41 | # Except, perhaps the License and Trove Classifiers!
42 | # If you do change the License, remember to change the Trove Classifier for that!
43 | here = os.path.abspath(os.path.dirname(__file__))
44 |
45 | # Import the README and use it as the long-description.
46 | # Note: this will only work if 'README.md' is present in your MANIFEST.in file!
47 | try:
48 | with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f:
49 | long_description = '\n' + f.read()
50 | except FileNotFoundError:
51 | long_description = DESCRIPTION
52 |
53 | # Load the package's __version__.py module as a dictionary.
54 | about = {}
55 | if not VERSION:
56 | project_slug = NAME.lower().replace("-", "_").replace(" ", "_")
57 | with open(os.path.join(here, project_slug, '__version__.py')) as f:
58 | exec(f.read(), about)
59 | else:
60 | about['__version__'] = VERSION
61 |
62 |
63 | class UploadCommand(Command):
64 | """Support setup.py upload."""
65 |
66 | description = 'Build and publish the package.'
67 | user_options = []
68 |
69 | @staticmethod
70 | def status(s):
71 | """Prints things in bold."""
72 | print('\033[1m{0}\033[0m'.format(s))
73 |
74 | def initialize_options(self):
75 | pass
76 |
77 | def finalize_options(self):
78 | pass
79 |
80 | def run(self):
81 | try:
82 | self.status('Removing previous builds…')
83 | rmtree(os.path.join(here, 'dist'))
84 | except OSError:
85 | pass
86 |
87 | self.status('Building Source and Wheel (universal) distribution…')
88 | os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable))
89 |
90 | self.status('Uploading the package to PyPI via Twine…')
91 | os.system('py -m twine upload dist/*')
92 |
93 | self.status('Pushing git tags…')
94 | os.system('git tag v{0}'.format(about['__version__']))
95 | os.system('git push --tags')
96 |
97 | sys.exit()
98 |
99 |
100 | # Where the magic happens:
101 | setup(
102 | name=NAME,
103 | version=about['__version__'],
104 | description=DESCRIPTION,
105 | long_description=long_description,
106 | long_description_content_type='text/markdown',
107 | author=AUTHOR,
108 | author_email=EMAIL,
109 | python_requires=REQUIRES_PYTHON,
110 | url=URL,
111 | packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]),
112 | # If your package is a single module, use this instead of 'packages':
113 | # py_modules=['mypackage'],
114 |
115 | entry_points={
116 | 'console_scripts': ['pyvogen=vogen.__main__:main'],
117 | },
118 | install_requires=REQUIRED,
119 | extras_require=EXTRAS,
120 | include_package_data=True,
121 | license='MIT License',
122 | classifiers=[
123 | # Trove classifiers
124 | # Full list: https://pypi.python.org/pypi?%3Aaction=list_classifiers
125 | 'License :: OSI Approved :: MIT License',
126 | 'Programming Language :: Python',
127 | 'Programming Language :: Python :: 3',
128 | 'Programming Language :: Python :: 3.9',
129 | 'Programming Language :: Python :: 3.8',
130 | 'Programming Language :: Python :: 3.7',
131 | 'Programming Language :: Python :: 3.6',
132 | 'Programming Language :: Python :: 3.5',
133 | 'Topic :: Multimedia :: Sound/Audio :: MIDI',
134 | "Topic :: Multimedia :: Sound/Audio :: Sound Synthesis",
135 | 'Programming Language :: Python :: Implementation :: CPython'
136 | ],
137 | # $ setup.py publish support.
138 | cmdclass={
139 | 'upload': UploadCommand,
140 | },
141 | )
--------------------------------------------------------------------------------
/up.bat:
--------------------------------------------------------------------------------
1 | python setup.py upload
2 | git push gitee --tags
3 | git push bitbucket --tags
--------------------------------------------------------------------------------
/vogen/.idea/.gitignore:
--------------------------------------------------------------------------------
1 | # Default ignored files
2 | /shelf/
3 | /workspace.xml
4 | # Datasource local storage ignored files
5 | /../../../../../:\Python39\Lib\vogen\.idea/dataSources/
6 | /dataSources.local.xml
7 | # Editor-based HTTP Client requests
8 | /httpRequests/
9 |
--------------------------------------------------------------------------------
/vogen/.idea/inspectionProfiles/profiles_settings.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/vogen/.idea/misc.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
--------------------------------------------------------------------------------
/vogen/.idea/modules.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/vogen/.idea/vogen.iml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/vogen/__init__.py:
--------------------------------------------------------------------------------
1 | """PyVogen:开源开源歌声合成引擎Vogen的python实现"""
2 |
3 | __version__='0.0.7'
4 |
5 | import copy
6 | import json
7 | import math
8 | import zipfile
9 | import jyutping
10 | import pypinyin
11 | import more_itertools
12 | from vogen import config
13 | from typing import List,Tuple,Dict,Union,Optional,Any
14 | from collections.abc import Callable,Iterator
15 |
16 | #导入numpy(可选)
17 | try:
18 | import numpy
19 | hasnumpy=True
20 | except ModuleNotFoundError:
21 | hasnumpy=False
22 | """
23 | #导入vogen.synth(可选)
24 | try:
25 | from vogen.synth import timetable
26 | hassynth=True
27 | except ModuleNotFoundError:
28 | hassynth=False
29 | """
30 | class VogNote():
31 | """
32 | Vogen音符对象
33 | pitch:音高,C4=60
34 | lyric:歌词汉字
35 | rom:歌词拼音
36 | on:开始时间,1拍=480
37 | dur:时长,1拍=480
38 | """
39 | def __init__(self,
40 | pitch:int=60,
41 | lyric:str="",
42 | rom:str="a",
43 | on:int=0,
44 | dur:int=480):
45 | self.pitch=pitch
46 | self.lyric=lyric
47 | self.rom=rom
48 | self.on=on
49 | self.dur=dur
50 |
51 | def __str__(self):
52 | return " VogNote {} {}[{}] {} {}\n".format(self.pitch,self.lyric,self.rom,self.on,self.dur)
53 |
54 | __repr__=__str__
55 |
56 | def dump(self)->dict:
57 | return self.__dict__
58 |
59 | def parsenote(content:dict)->VogNote:
60 | """
61 | 将音符字典解析为VogNote对象
62 | """
63 | vn=VogNote()
64 | vn.__dict__.update(content)
65 | return vn
66 |
67 | class VogUtt():
68 | """
69 | Vogen乐句对象
70 | singerId:歌手
71 | romScheme:语种(man:中文普通话,yue:粤语,yue-wz:粤语梧州话)
72 | notes:音符列表
73 | f0:音高线(不支持写入文件)
74 | """
75 | def __init__(self,
76 | singerId:str=config.config["DefaultSinger"],
77 | romScheme:str=config.config["DefaultRomScheme"],
78 | notes:Optional[List[VogNote]]=None,
79 | f0:Optional[numpy.ndarray]=None):
80 | self.singerId=singerId
81 | self.romScheme=romScheme
82 | if(notes==None):
83 | self.notes=[]
84 | else:
85 | self.notes=notes
86 | self.f0=f0
87 |
88 | def __add__(self,other):
89 | #两个乐句相加可合并乐句
90 | #self为上一乐句,other为下一乐句
91 | return VogUtt(singerId=self.singerId,romScheme=self.romScheme,notes=self.notes.copy()+other.notes.copy())
92 |
93 | def __radd__(self,other):
94 | #为适配sum,规定:0+VogUtt返回原VogUtt的副本
95 | if(type(other)==int and other==0):
96 | return copy.deepcopy(self)
97 | else:
98 | return NotImplemented
99 |
100 | def __str__(self)->str:
101 | return " VogUtt {} {} \n".format(self.singerId,self.romScheme)+"".join(str(n) for n in self.notes)
102 |
103 | __repr__=__str__
104 |
105 | def __len__(self)->int:
106 | """
107 | 获取乐句中的音符数量
108 | """
109 | return len(self.notes)
110 |
111 | def __getitem__(self,key)->VogNote:
112 | if(type(key)==slice):#如果是切片类型,则返回切片后的VogUtt对象
113 | return VogUtt(singerId=self.singerId,romScheme=self.romScheme,notes=self.notes[key])
114 | else:#如果key是整数,则返回音符
115 | return self.notes[key]
116 |
117 | def __setitem__(self,key,value):
118 | self.notes[key]=value
119 |
120 | def __delitem__(self,key):
121 | self.notes.__delitem__(key)
122 |
123 | def __iter__(self):
124 | return iter(self.notes)
125 |
126 | def __reversed__(self):
127 | return reversed(self.notes)
128 |
129 | def dump(self)->dict:
130 | """
131 | 将乐句对象转为vog文件中的字典形式
132 | """
133 | d=self.__dict__.copy()
134 | d.pop("f0")
135 | d["notes"]=[i.dump() for i in self.notes]
136 | return d
137 |
138 | def autosplit(self,r:int=80)->list:
139 | """
140 | 自动拆分乐句,两个音符间隙大于r则拆分,返回拆分后的乐句列表
141 | """
142 | indexs=[None]+[i+1 for (i,(note,nextnote)) in enumerate(more_itertools.pairwise(self)) if (note.on+note.dur+r=nextnote.on):
197 | note.dur=nextnote.on+nextnote.dur-note.on
198 | del self.notes[length-i-1]
199 | return self
200 |
201 | def getlyric(self)->List[str]:
202 | """
203 | 获取乐句中所有音符的歌词(字符串列表)
204 | """
205 | return [note.lyric for note in self.notes]
206 |
207 | def getrom(self)->List[str]:
208 | """
209 | 获取乐句中所有音符的拼音(字符串列表)
210 | """
211 | return [note.rom for note in self.notes]
212 |
213 | def setlyric(self,lyrics:Iterator[str]):
214 | """
215 | 批量灌入歌词,并自动转换为拼音
216 | lyrics:要输入的歌词,可以是字符串,也可以是字符串列表
217 | """
218 | for (note,lyric) in zip(self.notes,lyrics):
219 | note.lyric=lyric
220 | return self.lyrictorom()
221 |
222 | def transpose(self,value:int):
223 | """
224 | 移调
225 | """
226 | for note in self.notes:
227 | note.pitch+=value
228 | return self
229 |
230 | def copy(self):
231 | """
232 | 将乐句复制到剪贴板,可粘贴到vogen编辑器
233 | """
234 | import pyperclip
235 | f=copy.deepcopy(self).sort()
236 | offset=f[0].on
237 | for note in f:
238 | note.on-=offset
239 | pyperclip.copy(json.dumps({"utts":[f.dump()]}))
240 |
241 | def music21StreamToVogUtt(st)->VogUtt:
242 | """
243 | 将music21 stream对象转为vogen utt对象
244 | """
245 | import music21
246 | vognote=[]
247 | for note in st.flat.getElementsByClass(music21.note.Note):
248 | if(note.lyric in (None,"")):#连音符在music21中没有歌词
249 | lyric="-"
250 | else:
251 | lyric=note.lyric
252 | vognote.append(VogNote(on=int(note.offset*480),
253 | dur=int(note.duration.quarterLength*480),
254 | pitch=note.pitch.midi,
255 | lyric=lyric,
256 | rom=lyric))
257 | return VogUtt(notes=vognote).lyrictorom()
258 |
259 | def utaupyUstToVogUtt(u)->VogUtt:
260 | """
261 | 将utaupy ust对象转为vogen utt对象
262 | """
263 | vognotes=[]
264 | time=0
265 | for un in u.notes:
266 | notelen=int(un["Length"])
267 | if(not(un.lyric in ("","R","r"))):
268 | vognotes.append(VogNote(pitch=un["NoteNum"],lyric=un["Lyric"],rom=un["Lyric"],on=time,dur=notelen))
269 | time+=notelen
270 | return VogUtt(notes=vognotes)
271 |
272 | def midoMidiTrackToVogUtt(mt,ticks_per_beat:int=480)->VogUtt:
273 | """
274 | 将mido miditrack对象转为vogen utt对象
275 | """
276 | tick=0
277 | lyric=""
278 | note:Dict[int,Tuple[str,int]]={}#音符注册表 {音高:(歌词,开始时间)}
279 | vognotes=[]
280 | for signal in mt:
281 | tick+=signal.time
282 | if(signal.type=="note_on"):
283 | #将新音符注册到键位-音符字典中
284 | note[signal.note]=(lyric,tick)
285 | elif(signal.type=="note_off"):
286 | #从键位-音符字典中找到音符,并弹出
287 | if(signal.note in note):
288 | n=note.pop(signal.note)
289 | vognotes.append(VogNote(on=n[1]*480//ticks_per_beat,
290 | dur=(tick-n[1])*480//ticks_per_beat,
291 | pitch=signal.note,
292 | lyric=n[0],
293 | rom=n[0]))
294 | elif(signal.type=="lyrics"):
295 | lyric=signal.text
296 | return VogUtt(notes=vognotes).lyrictorom()
297 |
298 | def toVogUtt(a)->VogUtt:
299 | """
300 | 将其他类型的音轨工程对象转为vogen utt对象
301 | """
302 | type_name=type(a).__name__
303 | #从对象类型到所调用函数的字典
304 | type_function_dict={
305 | "VogUtt":copy.deepcopy,
306 | "Stream":music21StreamToVogUtt,#Music21普通序列对象
307 | "Measure":music21StreamToVogUtt,#Music21小节对象
308 | "Part":music21StreamToVogUtt,#Music21多轨中的单轨对象
309 | "Ust":utaupyUstToVogUtt,#utaupy ust对象
310 | }
311 | #如果在这个字典中没有找到函数,则默认调用a.toVogUtt()
312 | return type_function_dict.get(type_name,lambda x:x.toVogFile())(a)
313 |
314 | def parseutt(content:dict):
315 | """
316 | 将乐句字典解析为VogUtt对象
317 | """
318 | vu=VogUtt()
319 | vu.__dict__.update(content)
320 | vu.notes=[parsenote(i) for i in vu.notes]
321 | return vu
322 |
323 | class VogFile():
324 | """
325 | vogen工程对象
326 | timeSig0:节拍
327 | bpm0:曲速
328 | accomOffset:伴奏起点
329 | utts:乐句列表
330 | """
331 | def __init__(self,
332 | timeSig0:str="4/4",
333 | bpm0:float=120.0,
334 | accomOffset:int=0,
335 | utts:Optional[List[VogUtt]]=None):
336 | self.timeSig0=timeSig0
337 | self.bpm0=bpm0
338 | self.accomOffset=accomOffset
339 | if(utts==None):
340 | self.utts=[]
341 | else:
342 | self.utts=utts
343 |
344 | def __add__(self,other):
345 | result=copy.deepcopy(self)
346 | result.utts+=other.utts
347 | return result
348 |
349 | def __radd__(self,other):
350 | #为适配sum,规定:0+VogUtt返回原VogUtt的副本
351 | if(type(other)==int and other==0):
352 | return copy.deepcopy(self)
353 | else:
354 | return NotImplemented
355 |
356 | def __str__(self):
357 | return "VogFile {} {}\n".format(self.timeSig0,self.bpm0)+"".join(str(utt) for utt in self.utts)
358 |
359 | __repr__=__str__
360 |
361 | def __len__(self)->int:
362 | """
363 | 获取工程中的乐句数量
364 | """
365 | return len(self.utts)
366 |
367 | def __getitem__(self,key)->VogNote:
368 | if(type(key)==slice):#如果是切片类型,则返回切片后的VogUtt对象
369 | return VogFile(timeSig0=self.timeSig0,bpm0=self.bpm0,accomOffset=self.accomOffset,utts=self.utts[key])
370 | else:#如果key是整数,则返回乐句
371 | return self.utts[key]
372 |
373 | def __setitem__(self,key,value):
374 | self.utts[key]=value
375 |
376 | def __delitem__(self,key):
377 | self.utts.__delitem__(key)
378 |
379 | def __iter__(self):
380 | return iter(self.utts)
381 |
382 | def __reversed__(self):
383 | return reversed(self.utts)
384 |
385 | def append(self,utt:VogUtt):
386 | self.utts.append(utt)
387 | return self
388 |
389 | def dump(self)->dict:
390 | """
391 | 将乐句对象转为vog文件中的字典形式
392 | """
393 | d=self.__dict__.copy()
394 | d["utts"]=[i.dump() for i in self.utts]
395 | for (i,utt) in enumerate(d["utts"]):
396 | utt["name"]="utt-{}".format(i)
397 | return d
398 |
399 | def save(self,filename:str):
400 | """
401 | 保存文件
402 | """
403 | with zipfile.ZipFile(filename,"w") as z:
404 | z.writestr("chart.json",json.dumps(self.dump()))
405 |
406 | def autosplit(self,r:int=80):
407 | """
408 | 自动拆分乐句,两个音符间隙大于r则拆分
409 | """
410 | self.utts=sum((i.autosplit() for i in self.utts),[])
411 | return self
412 |
413 | def sortnote(self):
414 | """
415 | 对工程中的每个乐句,分别对其中的音符排序
416 | """
417 | for utt in self.utts:
418 | utt.sort()
419 | return self
420 |
421 | def sort(self):
422 | """
423 | 对工程文件中的乐句排序
424 | """
425 | self.sortnote()
426 | self.utts.sort(key=lambda x:x.notes[0].on)
427 | return self
428 |
429 | def setSinger(self,singerId:str):
430 | """
431 | 为工程中的所有乐句统一设置歌手
432 | """
433 | for utt in self.utts:
434 | utt.singerId=singerId
435 | return self
436 |
437 | def setRomScheme(self,romScheme:str):
438 | """
439 | 为工程中的所有乐句统一设置语种
440 | """
441 | for utt in self.utts:
442 | utt.romScheme=romScheme
443 | return self.lyrictorom()
444 |
445 | def lyrictorom(self):
446 | """
447 | 从歌词生成拼音
448 | """
449 | for utt in self.utts:
450 | utt.lyrictorom()
451 | return self
452 |
453 | def synth(self):
454 | """
455 | 合成工程,以numpy数组形式返回音频
456 | """
457 | import vogen.synth
458 | return vogen.synth.synth(self)
459 |
460 | def play(self):
461 | """
462 | 合成工程并播放
463 | """
464 | import vogen.synth
465 | return vogen.synth.play(self)
466 |
467 | def separate_by_singer(self)->Dict[str,Any]:
468 | """
469 | 根据歌手拆分工程,返回{音源名:对应的部分工程}字典
470 | """
471 | emptyfile=copy.copy(self)
472 | emptyfile.utts=[]
473 | result=dict()
474 | for i in self:
475 | result[i.singerId]=result.get(i.singerId,copy.copy(emptyfile)).append(i)
476 | return result
477 |
478 | def autojoinnote(self):
479 | for utt in self:
480 | utt.autojoinnote()
481 | return self
482 |
483 | def toVogFile(self):
484 | return copy.deepcopy(self)
485 |
486 | def notes(self):
487 | """
488 | 工程对象中的音符迭代器
489 | 注意:不是按音符的时间顺序,而是按乐句在工程中的顺序和音符在乐句中的顺序
490 | 只有在乐句、音符按时间顺序排列(可用VogFile.sort函数排序),且没有乐句重叠时,该迭代器才按时间顺序
491 | """
492 | for utt in self:
493 | yield from utt
494 |
495 | def getlyric(self)->List[str]:
496 | """
497 | 获取工程中所有音符的歌词(字符串列表)
498 | """
499 | return [note.lyric for note in self.notes()]
500 |
501 | def getrom(self)->List[str]:
502 | """
503 | 获取工程中所有音符的拼音(字符串列表)
504 | """
505 | return [note.rom for note in self.notes()]
506 |
507 | def setlyric(self,lyrics:Iterator[str]):
508 | """
509 | 批量灌入歌词,并自动转换为拼音
510 | lyrics:要输入的歌词,可以是字符串,也可以是字符串列表
511 | """
512 | for (note,lyric) in zip(self.notes(),lyrics):
513 | note.lyric=lyric
514 | return self.lyrictorom()
515 |
516 | def transpose(self,value:int):
517 | """
518 | 移调
519 | """
520 | for utt in self.utts:
521 | utt.transpose(value)
522 | return self
523 |
524 | def copy(self):
525 | """
526 | 将工程复制到剪贴板,可粘贴到vogen编辑器
527 | """
528 | import pyperclip
529 | f=copy.deepcopy(self).sort()
530 | offset=f[0][0].on
531 | for note in f.notes():
532 | note.on-=offset
533 | pyperclip.copy(json.dumps(f.dump()))
534 |
535 | def music21StreamToVogFile(st)->VogFile:
536 | """
537 | 将music21 stream对象转为vogen工程对象
538 | 注意:输入的music21 Stream对象不能有音符重叠
539 | """
540 | import music21
541 | vf=VogFile(utts=[music21StreamToVogUtt(st)]).autosplit()
542 | #节拍
543 | b=list(st.getElementsByClass(music21.meter.TimeSignature))
544 | if(len(b)>0):
545 | b=b[0]
546 | vf.timeSig0=("{}/{}".format(b.numerator,b.denominator))
547 | #曲速
548 | t=list(st.getElementsByClass(music21.tempo.MetronomeMark))
549 | if(len(t))>0:
550 | vf.bpm0=t[0].number
551 | return vf
552 |
553 | def music21ScoreToVogFile(sc)->VogFile:
554 | """
555 | 将music21 score对象转为vogen工程对象
556 | 注意:输入的music21 score对象中,音轨内不能有音符重叠
557 | """
558 | return sum([music21StreamToVogFile(part) for part in sc.parts]).sort()
559 |
560 | def utaupyUstToVogFile(u)->VogFile:
561 | """
562 | 将utaupy ust对象转为vogen工程对象
563 | """
564 | return VogFile(utts=[utaupyUstToVogUtt(u)],bpm0=float(u.tempo)).autosplit()
565 |
566 | def getTempoFromMidoMidiFile(mf)->float:
567 | """从mido.MidiFile获取曲速"""
568 | import mido
569 | for track in mf.tracks:
570 | for msg in track:
571 | if(hasattr(msg,"tempo")):
572 | return mido.tempo2bpm(msg.tempo)
573 | return 120.0
574 |
575 | def midoMidiTrackToVogFile(mt,ticks_per_beat:int=480)->VogFile:
576 | vu=midoMidiTrackToVogUtt(mt,ticks_per_beat)
577 | if(len(vu)>0):
578 | utts=[vu]
579 | else:
580 | utts=[]
581 | return VogFile(utts=utts).autosplit()
582 |
583 | def midoMidiFileToVogFile(mf)->VogFile:
584 | result=sum([midoMidiTrackToVogFile(tr,mf.ticks_per_beat) for tr in mf.tracks]).sort()
585 | result.tempo=getTempoFromMidoMidiFile(mf)
586 | return result
587 |
588 | def musicpyChordToVogFile(mpychord,start_time=0)->VogFile:
589 | utt=VogUtt()
590 | time=start_time
591 | for (note,interval) in zip(mpychord,mpychord.interval):
592 | utt.notes.append(VogNote(pitch=note.degree,
593 | on=int(time*1920),
594 | dur=int(note.duration*1920)))
595 | time+=interval
596 | print(len(utt))
597 | return VogFile(utts=[utt]).autosplit()
598 |
599 | def musicpyTrackToVogFile(mpytrack)->VogFile:
600 | return musicpyChordToVogFile(mpytrack.content,start_time=mpytrack.start_time)
601 |
602 | def toVogFile(a)->VogFile:
603 | """
604 | 将其他类型的音轨工程对象a转为vogen工程对象
605 | """
606 | type_name=type(a).__name__
607 | #从对象类型到所调用函数的字典
608 | type_function_dict={
609 | "VogFile":copy.deepcopy,
610 | "Stream":music21StreamToVogFile,#Music21普通序列对象
611 | "Measure":music21StreamToVogFile,#Music21小节对象
612 | "Part":music21StreamToVogFile,#Music21多轨中的单轨对象
613 | "Score":music21ScoreToVogFile,#Music21多轨工程对象
614 | "Ust":utaupyUstToVogFile,#utaupy ust对象
615 | "chord":musicpyChordToVogFile,#musicpy chord对象
616 | "track":musicpyTrackToVogFile,#musicpy track对象
617 | #TODO:musicpy score
618 | }
619 | #如果在这个字典中没有找到函数,则默认调用a.toVogFile()
620 | return type_function_dict.get(type_name,lambda x:x.toVogFile())(a)
621 |
622 | def parsefile(content:dict)->VogFile:
623 | """
624 | 将工程字典解析为VogFile对象
625 | """
626 | vf=VogFile()
627 | vf.__dict__.update(content)
628 | vf.utts=[parseutt(i) for i in vf.utts]
629 | return vf
630 |
631 | def parsef0(fp)->numpy.ndarray:
632 | """
633 | 解析工程文件中的f0文件
634 | """
635 | rawarray=numpy.frombuffer(fp,dtype=numpy.int)
636 | octave=rawarray//8388608-130
637 | #b=math.log2((rawarray%8388608)+8388608)*12-276.3763155
638 | key=numpy.log2((rawarray%8388608)+8388608)*12-276.3763155
639 | return (octave*12+key)*(rawarray!=0)
640 |
641 | def openvog(filename:str,loadf0:bool=True)->VogFile:
642 | """
643 | 打开vog文件,返回VogFile对象
644 | loadf0:是否加载音高线,默认为True(仅当loadf0=True且存在numpy库时加载)
645 | """
646 | with zipfile.ZipFile(filename, "r") as z:
647 | znamelist=z.namelist()
648 | vf=parsefile(json.loads(z.read("chart.json")))
649 | #uttdict={u.name:u for u in vf.utts}
650 | #加载音高线
651 | if(hasnumpy and loadf0):
652 | for i in range(len(vf.utts)):
653 | if("utt-{}.f0".format(i) in znamelist):
654 | vf.utts[i].f0=parsef0(z.open("utt-{}.f0".format(i)).read())
655 | return vf
656 |
657 | def loadfile_ust(filename:str)->VogFile:
658 | """
659 | 读入ust文件,并转为VogFile对象
660 | """
661 | import utaupy
662 | return toVogFile(utaupy.ust.load(filename))
663 |
664 | def loadfile_music21(filename:str)->VogFile:
665 | """
666 | 读入music21支持的文件,并转为VogFile对象
667 | """
668 | import music21
669 | return music21ScoreToVogFile(music21.converter.parse(filename))
670 |
671 | def loadfile_musicxml(filename:str)->VogFile:
672 | """
673 | 读入musicxml文件,并转为VogFile对象
674 | """
675 | return loadfile_music21(filename).autojoinnote()
676 |
677 | def loadfile_mid(filename:str)->VogFile:
678 | import mido
679 | return midoMidiFileToVogFile(mido.MidiFile(filename))
680 |
681 | def loadfile(filename:str)->VogFile:
682 | """
683 | 读入文件,并转为VogFile对象
684 | 支持的文件类型:vog,ust,musicxml,mxl,mid
685 | """
686 | filetype=filename.split(".")[-1].lower()
687 | fileparsers:Dict[str,Callable[[str],VogFile]]={
688 | "vog":openvog,
689 | "ust":loadfile_ust,
690 | "musicxml":loadfile_musicxml,
691 | "mxl":loadfile_musicxml,
692 | "mid":loadfile_mid,
693 | }
694 | return fileparsers[filetype](filename)
695 |
696 | def paste()->VogFile:
697 | """
698 | 获取剪贴板中的工程片段
699 | """
700 | import pyperclip
701 | return parsefile(json.loads(pyperclip.paste()))
702 |
703 | #关于f0格式
704 | #np.fromfile("utt-0.f0",dtype=np.int)
705 | #以八度为单位,八度之间等差,差2**23
706 | #八度之内为等比,(x+1)%2**23的公比为2**(1/12)(十二平均律常数)
707 | #TODO:f0的时间单位
708 |
709 | #调试
710 | def main():
711 | openvog(r"C:\users\lin\desktop\shl.vog")
712 |
713 | if(__name__=="__main__"):
714 | main()
--------------------------------------------------------------------------------
/vogen/__main__.py:
--------------------------------------------------------------------------------
1 | """PyVogen命令行接口"""
2 |
3 | import json
4 | import vogen
5 | from typing import List
6 | import argparse
7 | Parser=argparse.ArgumentParser
8 |
9 | def main():
10 | #显示默认帮助
11 | def pyvogen_default(args):
12 | print("PyVogen命令行工具\n\npm 包管理器\nversion 显示版本信息\n\n可在此找到更多帮助:https://gitee.com/oxygendioxide/vogen")
13 | parser = Parser(prog='pyvogen')
14 | #print(parser)
15 | parser.set_defaults(func=pyvogen_default)
16 | subparsers = parser.add_subparsers(help='sub-command help')
17 |
18 | #显示版本信息
19 | def showversion(args):
20 | import sys
21 | import onnxruntime
22 | print("pyvogen version: {}".format(vogen.__version__))
23 | print("onnxruntime version: {}".format(onnxruntime.__version__))
24 | print("python version: {}".format(sys.version))
25 | parser_version=subparsers.add_parser("version",help="显示版本信息")
26 | parser_version.set_defaults(func=showversion)
27 |
28 | #包管理器
29 | parser_pm=subparsers.add_parser("pm",help="包管理器")
30 | subparsers_pm=parser_pm.add_subparsers(help='')
31 | #安装
32 | def pm_install(args):
33 | from vogen import pm
34 | install_func=pm.install
35 | if(args.local):
36 | install_func=pm.install_local
37 | elif(args.online):
38 | install_func=pm.install_online
39 | for i in args.name:
40 | install_func(i,force=args.force)
41 | parser_pm_install=subparsers_pm.add_parser("install",help="安装")
42 | parser_pm_install.add_argument('name',type=str,nargs='+')
43 | parser_pm_install.add_argument('-l',"--local",action='store_true',help='从本地包安装')
44 | parser_pm_install.add_argument('-o',"--online",action='store_true',help="下载在线包并安装")
45 | parser_pm_install.add_argument('-F',"--force",action='store_true',help="强制覆盖现有文件")
46 | parser_pm_install.set_defaults(func=pm_install)
47 | #列出已安装音源
48 | def pm_list(args):
49 | from vogen import pm
50 | pkglist=pm.list()
51 | if(args.json):
52 | print(json.dumps([{"name":i} for i in pkglist]))
53 | else:
54 | print("\n".join(pkglist))
55 | parser_pm_list=subparsers_pm.add_parser("list",help="列出已安装音源")
56 | parser_pm_list.set_defaults(func=pm_list)
57 | parser_pm_list.add_argument("-j","--json",action='store_true',help="以json格式输出")
58 |
59 | #卸载
60 | def pm_uninstall(args):
61 | from vogen import pm
62 | pm.uninstall(args.id)
63 | parser_pm_uninstall=subparsers_pm.add_parser("uninstall",help="卸载")
64 | parser_pm_uninstall.add_argument("id")
65 | parser_pm_uninstall.set_defaults(func=pm_uninstall)
66 |
67 | #设置
68 | def config(args):#输出当前设置
69 | from vogen import config
70 | from tabulate import tabulate
71 | if(args.json):
72 | print(json.dumps(config.config))
73 | else:
74 | print(tabulate(config.config.items(),headers=["Key","Value"]))
75 | parser_config=subparsers.add_parser("config",help="设置")
76 | parser_config.set_defaults(func=config)
77 | parser_config.add_argument("-j","--json",action='store_true',help="以json格式输出")
78 | subparsers_config=parser_config.add_subparsers(help='')
79 |
80 | #修改设置
81 | def config_set(args):
82 | from vogen import config
83 | config.set(args.key,args.value)
84 | parser_config_set=subparsers_config.add_parser("set",help="修改设置")
85 | parser_config_set.set_defaults(func=config_set)
86 | parser_config_set.add_argument('key',type=str)
87 | parser_config_set.add_argument('value',type=str)
88 |
89 | #合成
90 | def synth(args):
91 | import os
92 | import wavio
93 | from vogen import synth
94 | from vogen.synth import utils
95 | infile=args.infile
96 | if(args.outfile==""):
97 | outfile=infile[:-4]+".wav"
98 | else:
99 | outfile=args.outfile
100 | #如果输出文件已存在
101 | if(os.path.isfile(outfile)):
102 | print(outfile+" 已存在,是否覆盖?\ny:覆盖并合成 n:保留并放弃合成")
103 | instr=input()
104 | while(len(instr)==0 or not(instr[0] in ("y","n","Y","N"))):
105 | print("y:覆盖并合成 n:保留并放弃合成")
106 | instr=input()
107 | if(instr[0] in ("n","N")):
108 | return
109 | wavio.write(outfile,synth.synth(vogen.loadfile(infile,False)),utils.Params.fs)
110 |
111 | parser_synth=subparsers.add_parser("synth",help="合成")
112 | parser_synth.set_defaults(func=synth)
113 | parser_synth.add_argument("infile",type=str,help="输入文件")
114 | parser_synth.add_argument("outfile",type=str,nargs='?',default="",help="输出文件")
115 | parser_synth.add_argument("-F,--force",action="store_true",help="强制覆盖现有文件")
116 |
117 | args = parser.parse_args()
118 | #print(args)
119 | args.func(args)
120 |
121 | if(__name__=="__main__"):
122 | main()
--------------------------------------------------------------------------------
/vogen/config/__init__.py:
--------------------------------------------------------------------------------
1 | """
2 | PyVogen设置
3 | """
4 | from typing import Any,Dict
5 | import os
6 | import json
7 |
8 | #设置项说明
9 | #DefaultSinger:默认的SingerId
10 | #DefaultRomScheme:默认语言
11 |
12 | #出厂默认设置
13 | defaultConfig={"DefaultSinger":"",
14 | "DefaultRomScheme":"man",
15 | }
16 |
17 | #各设置项的变量类型
18 | configtype={"DefaultSinger":str,
19 | "DefaultRomScheme":str}
20 |
21 | configPath=os.path.join(os.path.split(os.path.realpath(__file__))[0],"config.json")#设置文件路径
22 |
23 | def write(config:Dict[str,Any]):
24 | """
25 | 将设置写入设置文件
26 | """
27 | global configPath
28 | json.dump(config,open(configPath,"w",encoding="utf8"))
29 |
30 | initConfig=defaultConfig#initConfig为vogen库本次刚刚导入时的设置,即设置文件
31 | try:
32 | initConfig.update(json.load(open(configPath)))
33 | except json.decoder.JSONDecodeError:
34 | write(initConfig)
35 | except FileNotFoundError:
36 | write(initConfig)
37 |
38 | config=initConfig
39 |
40 | def set(key:str,value:Any):
41 | """
42 | 修改单个设置项并写入设置文件
43 | """
44 | if(type(value)==str!=configtype[key]):
45 | if(configtype[key]==bool):
46 | value={"true":True,"t":True,"false":False,"f":False}[value.lower()]
47 | else:
48 | value=configtype[key](value)
49 | assert(type(value)==configtype[key])
50 | config[key]=value
51 | initConfig[key]=value
52 | write(initConfig)
--------------------------------------------------------------------------------
/vogen/config/config.json:
--------------------------------------------------------------------------------
1 | {"DefaultSinger": "Gloria"}
--------------------------------------------------------------------------------
/vogen/pm.py:
--------------------------------------------------------------------------------
1 | """PyVogen音源包管理器"""
2 |
3 | import os
4 | import json
5 | import wget
6 | import shutil
7 | import zipfile
8 | import tempfile
9 | from typing import Dict,List
10 |
11 | pkgroot=os.path.join(os.path.split(os.path.realpath(__file__))[0],"synth","libs")
12 |
13 | #由于windows文件夹不区分大小写,而Linux区分大小写
14 | #为保证跨平台一致性,在安装时所有包名均转为小写,调用时大小写均可
15 |
16 | def install_local(name:str,force:bool=False)->int:
17 | """
18 | 安装本地音源
19 | name:文件路径与名称
20 | force:是否强制覆盖安装
21 | """
22 | name=os.path.realpath(name)
23 | with zipfile.ZipFile(name, "r") as z:
24 | contents=z.namelist()
25 | #如果没有meta.json或model.onnx,则包不完整,返回1
26 | if(not("meta.json" in contents and "model.onnx" in contents)):
27 | return 1
28 | pid=json.loads(z.read("meta.json"))["id"].lower()
29 | pkgpath=os.path.join(pkgroot,pid)
30 | #如果目录已存在,则询问是否删除
31 | if(os.path.exists(pkgpath)):
32 | if(force):
33 | shutil.rmtree(pkgpath)
34 | else:
35 | print(pid+"已存在,是否删除?\ny:删除并继续安装 n:保留并放弃安装")
36 | instr=input()
37 | while(len(instr)==0 or not(instr[0] in ("y","n","Y","N"))):
38 | print("y:删除并继续安装 n:保留并放弃安装")
39 | instr=input()
40 | if(instr[0] in ("y","Y")):
41 | shutil.rmtree(pkgpath)
42 | else:
43 | return -1
44 |
45 | #创建目录
46 | os.makedirs(pkgpath)
47 | #解压文件
48 | orgcwd=os.getcwd()#先备份原工作路径,以便返回
49 | os.chdir(pkgpath)
50 | z.extractall()
51 | os.chdir(orgcwd)
52 | print(pid,"安装完成")
53 | return 0
54 |
55 | def install_online(url:str,force:bool=False):
56 | """
57 | 从url安装在线音源
58 | url:音源链接
59 | force:是否强制覆盖安装
60 | """
61 | path=os.path.join(tempfile.mkdtemp(),"temp.vogeon")
62 | wget.download(url,path)
63 | install_local(path,force)
64 |
65 | def install(name:str,force:bool=False):
66 | """
67 | 安装音源
68 | name:文件路径与名称
69 | """
70 | if(os.path.isfile(name)):
71 | install_local(name,force)
72 | else:
73 | install_online(name,force)
74 |
75 | def uninstall(pid:str):
76 | """
77 | 卸载音源
78 | pid:音源包名
79 | """
80 | pkgpath=os.path.join(pkgroot,pid)
81 | shutil.rmtree(pkgpath)
82 |
83 | def list()->List[str]:
84 | """
85 | 列出已安装的音源
86 | """
87 | names=[]
88 | for name in os.listdir(pkgroot):
89 | pkgpath=os.path.join(pkgroot,name)
90 | if(os.path.isfile(os.path.join(pkgpath,"meta.json")) and os.path.isfile(os.path.join(pkgpath,"model.onnx"))):
91 | names.append(name)
92 | return names
93 |
94 | def show(pid:str)->Dict[str,str]:
95 | """
96 | 音源详细信息
97 | pid:音源包名
98 | """
99 | return json.load(open(os.path.join(pkgroot,pid,"meta.json"),encoding="utf8"))
100 |
101 | def main():
102 | list()
103 |
104 | if(__name__=="__main__"):
105 | main()
--------------------------------------------------------------------------------
/vogen/synth/__init__.py:
--------------------------------------------------------------------------------
1 | """PyVogen合成引擎"""
2 | #基于Vogen 7月28日代码
3 |
4 | import tqdm
5 | import vogen
6 | import numpy
7 | import pyworld
8 | from typing import List,Optional
9 | from vogen.synth import f0
10 | from vogen.synth import utils
11 | from vogen.synth import prosody
12 | from vogen.synth import acoustics
13 | from vogen.synth import timetable
14 |
15 | params=utils.Params()
16 |
17 | def synthutt(utt:vogen.VogUtt,tempo:float):
18 | """
19 | 合成单个乐句,以numpy数组形式返回
20 | """
21 | def ticktotime(tick:int)->int:
22 | #将480为一拍的tick转为0.01s为单位的时间
23 | return int(100*tick/(8*tempo))
24 | tchars:List[timetable.TChar]=[timetable.TChar()]
25 | uttstart=utt.notes[0].on
26 | uttstarttime=ticktotime(uttstart)
27 | tick=uttstart
28 |
29 | for note in utt.notes:
30 | #如果音符前有空隙,则补充一个None
31 | if(note.on>tick):
32 | tchars.append(timetable.TChar())
33 | tnote=timetable.TNote(pitch=note.pitch,on=ticktotime(note.on)-uttstarttime+50,off=ticktotime(note.on+note.dur)-uttstarttime+50)
34 | if(note.rom=="-"):
35 | tchars[-1].notes.append(tnote)
36 | else:
37 | tchars.append(timetable.TChar(ch=note.lyric,rom=note.rom,notes=[tnote]))
38 | tick=note.on+note.dur
39 | tchars.append(timetable.TChar())
40 | tchars=prosody.run(utt.romScheme,tchars[-2].notes[-1].off+50,tchars)
41 | f0array=f0.run(utt.romScheme,tchars)#运行基频模型
42 | mgc,bap=acoustics.run(utt.romScheme,utt.singerId,f0array,tchars)#运行合成模型
43 | sp=pyworld.decode_spectral_envelope(mgc[0].astype(numpy.float64),params.fs,params.worldFftSize)
44 | ap=pyworld.decode_aperiodicity(bap[0].astype(numpy.float64),params.fs,params.worldFftSize)
45 | #import time###
46 | #start=time.time()###
47 | result=pyworld.synthesize(f0array.astype(numpy.float64),sp,ap,params.fs,params.hopSize.microseconds//1000)
48 | #end=time.time()###
49 | #print("pyworld用时:",end-start)###
50 | return result
51 |
52 | def synth(file):
53 | """
54 | 从工程对象合成音频,以numpy数组形式返回
55 | """
56 | if(type(file)!=vogen.VogFile):
57 | file=vogen.toVogFile(file)
58 | tempo=file.bpm0
59 | #以utt为单位合成
60 | nutt=len(file.utts)#utt的数量
61 | tracklen=max([utt.notes[-1].on+utt.notes[-1].dur for utt in file.utts])#以tick为单位的音轨总长度
62 | trackwave=numpy.zeros(int(params.fs*(tracklen/(8*tempo)+0.5)))
63 | for i,utt in enumerate(tqdm.tqdm(file.utts)):
64 | #显示进度
65 | #print("\r正在合成乐句{}/{}".format(i+1,nutt)+"#"*int(20*i/nutt)+"-"*(20-int(20*i/nutt)))
66 | #合成
67 | uttwave=synthutt(utt,tempo)
68 | #将合成出的音频加到音轨上
69 | uttoffset=int(params.fs*(utt.notes[0].on/(8*tempo)-0.5))
70 | #一般情况,utt的开头在音轨开头后面
71 | if(uttoffset>=0):
72 | trackwave[uttoffset:uttoffset+len(uttwave)]+=uttwave
73 | #但是如果音符紧挨着音轨开头,则辅音会伸到音轨开头前面
74 | else:
75 | trackwave[0:uttoffset+len(uttwave)]+=uttwave[-uttoffset:]
76 | return (trackwave*(2**15 - 1)).astype(numpy.int16)
77 |
78 |
79 | def synth_multithread(file:vogen.VogFile):
80 | """
81 | 实验性的多线程合成,节省不了多少时间,暂时不会维护,请勿使用
82 | """
83 | tempo=file.bpm0
84 | #以utt为单位合成
85 | nutt=len(file.utts)#乐句数量
86 | tracklen=max([utt.notes[-1].on+utt.notes[-1].dur for utt in file.utts])#以tick为单位的音轨总长度
87 | trackwave=numpy.zeros(int(params.fs*(tracklen/(8*tempo)+0.5)))
88 | nuttdone=[0]#已合成的乐句数量
89 | import threading
90 | lock = threading.Lock()#输出进度锁
91 | def synththread(i,trackwave,nutt,nuttdone,lock):#合成线程,i:utt序号
92 | utt=file.utts[i]
93 | uttwave=synthutt(utt,tempo)
94 | uttoffset=int(params.fs*(utt.notes[0].on/(8*tempo)-0.5))
95 | trackwave[uttoffset:uttoffset+len(uttwave)]+=uttwave
96 | lock.acquire()
97 | try:
98 | print("\r正在合成乐句{}/{}(序号:{})".format(nuttdone[0]+1,nutt,i)+"#"*int(20*nuttdone[0]/nutt)+"-"*(20-int(20*nuttdone[0]/nutt)))
99 | nuttdone[0]+=1
100 | finally:
101 | lock.release()
102 | synththreads=[threading.Thread(target=synththread, args=(i,trackwave,nutt,nuttdone,lock)) for i in range(nutt)]
103 | for thread in synththreads:
104 | thread.start()
105 | for thread in synththreads:
106 | thread.join()
107 | return (trackwave*(2**15 - 1)).astype(numpy.int16)
108 |
109 | def is_jupyter_notebook()->bool:
110 | """
111 | 检测是否为jupyter notebook
112 | """
113 | try:
114 | get_ipython().__class__.__name__
115 | #jupyter notebook
116 | return True
117 | except NameError:
118 | #普通命令行
119 | return False
120 |
121 | def play(file):
122 | """
123 | 从工程对象合成音频并播放
124 | """
125 | a=synth(file)
126 | if(is_jupyter_notebook()):
127 | from IPython.display import Audio
128 | return Audio(data=a, rate=params.fs)
129 | else:
130 | import simpleaudio as sa
131 | sa.play_buffer(a,1,2,params.fs)
132 |
133 | #测试
134 | def main():
135 | global a
136 | import time###
137 | start=time.time()###
138 | #a=synth(vogen.openvog(r"C:\users\lin\desktop\3.vog"))
139 | a=synth_multithread(vogen.openvog(r"C:\users\lin\desktop\3.vog"))
140 | end=time.time()###
141 | print("总用时:",end-start)###
142 | #print(a)
143 | #from myplot import plot
144 | #plot(a)
145 |
146 | #播放音频
147 | #import numpy as np
148 | #audio =(a*(2**15 - 1)/np.max(np.abs(a))).astype(np.int16)
149 | #plot(audio)
150 | #import simpleaudio as sa
151 | #sa.play_buffer(audio,1,2,44100)
152 |
153 | #导出音频
154 | import wavio
155 | import numpy as np
156 | wavio.write(r"C:\users\lin\desktop\3.wav",a,44100)
157 |
158 | if(__name__=="__main__"):
159 | main()
--------------------------------------------------------------------------------
/vogen/synth/acoustics.py:
--------------------------------------------------------------------------------
1 | """人声模型相关"""
2 | import os
3 | import numpy
4 | import onnxruntime
5 | from typing import List,Optional
6 | from vogen.synth import timetable,utils
7 |
8 | pkgroot=os.path.join(os.path.split(os.path.realpath(__file__))[0],"libs")
9 | modelmanager=utils.ModelManager(os.path.join(pkgroot,"{}","model.onnx"))
10 |
11 | def run(romScheme:str,voiceLibId:str,f0:numpy.ndarray,chars:List[timetable.TChar])->List[numpy.ndarray]:
12 | voiceLibId=voiceLibId.lower()
13 | phs:List[timetable.TPhoneme]=sum([ch.ipa for ch in chars],[])
14 | phDurs=[ph.off-ph.on for ph in phs]
15 | phSyms=[]
16 | for ph in phs:
17 | if(ph.ph==None):
18 | phSyms.append("")
19 | elif(":" in ph.ph):
20 | phSyms.append(ph.ph)
21 | else:
22 | phSyms.append(romScheme+":"+ph.ph)
23 | breAmp=numpy.array([numpy.zeros(len(f0),dtype=numpy.float32)])
24 | xs={"phDurs":numpy.array([phDurs],dtype=numpy.int64),
25 | "phs":numpy.array([phSyms]),
26 | "f0":numpy.array([f0],dtype=numpy.float32),
27 | "breAmp":breAmp}
28 | #载入音源
29 | model=modelmanager.get(voiceLibId)
30 | result=model.run([model.get_outputs()[0].name,model.get_outputs()[1].name],xs)
31 | return result
32 | #mgc,bap
--------------------------------------------------------------------------------
/vogen/synth/f0.py:
--------------------------------------------------------------------------------
1 | """基频模型相关"""
2 | import os
3 | import numpy
4 | import onnxruntime
5 | import more_itertools
6 | from typing import List,Optional
7 | from vogen.synth import timetable
8 |
9 | modelpath=os.path.join(os.path.split(os.path.realpath(__file__))[0],"models")
10 | models={"man":onnxruntime.InferenceSession(os.path.join(modelpath,"f0.man.onnx")),
11 | "yue":onnxruntime.InferenceSession(os.path.join(modelpath,"f0.yue.onnx")),
12 | "yue-wz":onnxruntime.InferenceSession(os.path.join(modelpath,"f0.yue.onnx"))}
13 |
14 | def run(romScheme:str,chars:List[timetable.TChar])->numpy.ndarray:
15 | phs=sum([ch.ipa for ch in chars],[])
16 | uttDur=chars[-1].ipa[-1].off
17 | noteOps:List[Optional[timetable.TNote]]=sum([([None] if ch.notes==None else ch.notes) for ch in chars],[])
18 | #print([None if n==None else n.__dict__ for n in noteOps])
19 | noteBounds=[0]
20 | for (prevNote,note) in more_itertools.pairwise(noteOps):
21 | if(note!=None):
22 | noteBounds.append(note.on)
23 | elif(prevNote!=None):
24 | noteBounds.append(prevNote.off)
25 | else:
26 | pass#raise ArgumentException("Consecutive sil chars in %A")
27 | noteBounds.append(uttDur)
28 | phSyms=[]
29 | for ph in phs:
30 | if(ph.ph==None):
31 | phSyms.append("")
32 | elif(":" in ph.ph):
33 | phSyms.append(ph.ph)
34 | else:
35 | phSyms.append(romScheme+":"+ph.ph)
36 | notePitches=[0.0 if note==None else note.pitch for note in noteOps]
37 | noteDurs=[(t1-t0) for (t0,t1) in more_itertools.pairwise(noteBounds)]
38 | noteToCharIndex=list(range(len(noteOps)))
39 | phDurs=[ph.off-ph.on for ph in phs]
40 | xs={"phs":numpy.array(phSyms),
41 | "notePitches":numpy.array(notePitches,dtype=numpy.float32),
42 | "noteDurs":numpy.array(noteDurs,dtype=numpy.int64),
43 | "noteToCharIndex":numpy.array(noteToCharIndex,dtype=numpy.int64),
44 | "phDurs":numpy.array(phDurs,dtype=numpy.int64)}
45 | #运行模型
46 | model=models[romScheme]
47 | f0=model.run([model.get_outputs()[0].name],xs)[0]
48 | return f0
49 |
50 | def main():
51 | import timetable as t
52 | chars=[t.TChar(ch=None,rom=None,notes=None,ipa=[t.TPhoneme(ph=None,on=0,off=39)]),
53 | t.TChar(ch="du",rom="du",notes=[t.TNote(pitch=60,on=50,off=75)],ipa=[t.TPhoneme("d",39,50),t.TPhoneme("u",50,66)]),
54 | t.TChar(ch="guang",rom="guang",notes=[t.TNote(pitch=62,on=75,off=100),t.TNote(60,100,125)],ipa=[t.TPhoneme("g",66,74),t.TPhoneme("w",74,85),t.TPhoneme("ag",85,103),t.TPhoneme("ngq",103,125)]),
55 | t.TChar(ch=None,rom=None,notes=None,ipa=[t.TPhoneme(None,125,175)]),
56 | ]
57 | f0=run("man",chars)
58 | from myplot import plot
59 | plot(f0)
60 |
61 | if(__name__=="__main__"):
62 | main()
--------------------------------------------------------------------------------
/vogen/synth/g2p.py:
--------------------------------------------------------------------------------
1 | """拼音转音素"""
2 | import os
3 | import numpy
4 | import onnxruntime
5 | from typing import List,Optional
6 |
7 | xLength=8
8 | yLength=4
9 |
10 | modelpath=os.path.join(os.path.split(os.path.realpath(__file__))[0],"models")
11 | models={"man":onnxruntime.InferenceSession(os.path.join(modelpath,"g2p.man.onnx")),
12 | "yue":onnxruntime.InferenceSession(os.path.join(modelpath,"g2p.yue.onnx")),
13 | "yue-wz":onnxruntime.InferenceSession(os.path.join(modelpath,"g2p.yue-wz.onnx"))}
14 |
15 | #拼音列表转为n*8的tensor,每个拼音一行,8字节,后面用"\0"补齐
16 | def runForScheme(romScheme:str,roms:List[str])->List[List[str]]:
17 | xs=numpy.array([list(i.ljust(8,"\0")) for i in roms])
18 | model=models[romScheme]
19 | #代入模型运行
20 | ys=model.run([model.get_outputs()[0].name],{model.get_inputs()[0].name:xs})[0]
21 | #返回值示例:ys=[['d', 'u','',''], ['g', 'w', 'ag', 'ngq']]
22 | #过滤空字符串
23 | return [[j for j in i if j!=""] for i in ys]
24 |
25 | def run(romScheme:str,roms:List[Optional[str]]):
26 | #let schemeToRomIndices =
27 | #建立"语言->哪些音符是这个语言(列表)"的字典
28 | schemeToRomIndices=dict()
29 | for i,rom in enumerate(roms):
30 | #用冒号临时改变一个音的语言
31 | if(rom!=None):
32 | if(":" in rom):
33 | currRomScheme=rom.split(":")[0]
34 | else:
35 | currRomScheme=romScheme
36 | schemeToRomIndices[currRomScheme]=schemeToRomIndices.get(currRomScheme,[])+[i]
37 | #TODO
38 | #let romsNoPrefix =
39 | #删除语言前缀
40 | romsNoPrefix=[None if rom==None else rom.split(":")[-1] for rom in roms]
41 |
42 | phs=[None]*len(roms)
43 | #for KeyValue(currRomScheme, romIndices) in schemeToRomIndices do
44 | for currRomScheme in schemeToRomIndices:
45 | romIndices=schemeToRomIndices[currRomScheme]
46 | schemeRoms=[romsNoPrefix[i] for i in romIndices]
47 | #print(schemeRoms,romIndices)
48 | #input()
49 | schemePhs=runForScheme(currRomScheme,schemeRoms)
50 | for (romIndex, schemePh) in zip(romIndices,schemePhs):
51 | if (currRomScheme==romScheme):
52 | phs[romIndex]=schemePh
53 | else:
54 | phs[romIndex]=[ph if (ph in ("",None)) else currRomScheme+":"+ph for ph in schemePh]
55 | return phs
56 |
57 | def main():
58 | print(run("man",["yue:du","guang"]))
59 |
60 | if(__name__=="__main__"):
61 | main()
--------------------------------------------------------------------------------
/vogen/synth/libs/.gitkeep:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/libs/.gitkeep
--------------------------------------------------------------------------------
/vogen/synth/models/f0.man.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/models/f0.man.onnx
--------------------------------------------------------------------------------
/vogen/synth/models/f0.yue.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/models/f0.yue.onnx
--------------------------------------------------------------------------------
/vogen/synth/models/g2p.man.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/models/g2p.man.onnx
--------------------------------------------------------------------------------
/vogen/synth/models/g2p.yue-wz.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/models/g2p.yue-wz.onnx
--------------------------------------------------------------------------------
/vogen/synth/models/g2p.yue.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/models/g2p.yue.onnx
--------------------------------------------------------------------------------
/vogen/synth/models/po.man.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/models/po.man.onnx
--------------------------------------------------------------------------------
/vogen/synth/models/po.yue.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/models/po.yue.onnx
--------------------------------------------------------------------------------
/vogen/synth/models/rfft.onnx:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oxygen-dioxide/vogen/69100e3b02c0b550501b5aa61091d96d5fc8747c/vogen/synth/models/rfft.onnx
--------------------------------------------------------------------------------
/vogen/synth/prosody.py:
--------------------------------------------------------------------------------
1 | """确定音素时长"""
2 | import os
3 | import copy
4 | import numpy
5 | import datetime
6 | import onnxruntime
7 | import more_itertools
8 | from typing import List
9 | from vogen.synth import g2p
10 | from vogen.synth import timetable
11 |
12 | modelpath=os.path.join(os.path.split(os.path.realpath(__file__))[0],"models")
13 | models={"man":onnxruntime.InferenceSession(os.path.join(modelpath,"po.man.onnx")),
14 | "yue":onnxruntime.InferenceSession(os.path.join(modelpath,"po.yue.onnx")),
15 | "yue-wz":onnxruntime.InferenceSession(os.path.join(modelpath,"po.yue.onnx"))}
16 |
17 | def run(romScheme:str,uttDur:int,chars:List[timetable.TChar]):
18 | roms=[i.rom for i in chars]
19 | chPhs=g2p.run(romScheme,roms)
20 | chPhs=[([None] if i==None else i) for i in chPhs]
21 | phs=[]
22 | for i in chPhs:
23 | for ph in i:
24 | if(ph==None):
25 | phs.append("")
26 | elif(":" in ph):
27 | phs.append(ph)
28 | else:
29 | phs.append(romScheme+":"+ph)
30 | #print(phs)
31 | chPhCounts=[len(i) for i in chPhs]
32 | noteBounds=[0]
33 | for (ch0,ch1) in more_itertools.pairwise(chars):
34 | if ch1.notes!=None:
35 | noteBounds.append(ch1.notes[0].on)
36 | else:
37 | noteBounds.append(ch0.notes[-1].off)
38 | noteBounds.append(uttDur)
39 | noteBoundsSec=[timetable.frameToTime(i).total_seconds() for i in noteBounds]
40 | noteDursSec=[nt1-nt0 for (nt0,nt1) in more_itertools.pairwise(noteBoundsSec)]
41 | xs={"phs":numpy.array(phs),"chPhCounts":numpy.array(chPhCounts,dtype=numpy.int64),"noteDursSec":numpy.array(noteDursSec,dtype=numpy.float32)}
42 | #print(xs)
43 | #运行模型
44 | model=models[romScheme]
45 | ys=model.run([model.get_outputs()[0].name],xs)[0]
46 | #print(ys)
47 | phBoundsSec=ys.tolist()
48 | #TODO
49 | #跑出来的结果可能会有dur<=0,修复
50 | #minPhDurSec=timetable.frameToTime(1.01).total_seconds()
51 | #for (nt0,nt1) in zip(noteBoundsSec[-2::-1],noteBoundsSec[-1:0:-1]):
52 | # print(nt0,nt1)
53 | phBounds=[int(round(timetable.timeToFrame(datetime.timedelta(seconds=i)))) for i in phBoundsSec]
54 | chPhIndexBounds=[0]
55 | for i in chPhCounts:
56 | chPhIndexBounds.append(chPhIndexBounds[-1]+i)
57 |
58 | chPhBounds=[phBounds[startIndex:endIndex+1] for (startIndex, endIndex) in more_itertools.pairwise(chPhIndexBounds)]
59 | #print(chPhBounds)
60 | outChars=copy.deepcopy(chars)
61 | for (char,phs,phBounds) in zip(outChars,chPhs,chPhBounds):
62 | char.ipa=[timetable.TPhoneme(ph=ph,on=on,off=off) for (ph,on,off) in zip(phs,phBounds[0:-1],phBounds[1:])]
63 | return outChars
64 |
65 |
66 | def main():
67 | import timetable as t
68 | chars=[t.TChar(ch=None,rom=None,notes=None,ipa=None),
69 | t.TChar(ch="du",rom="du",notes=[t.TNote(pitch=60,on=50,off=75)],ipa=None),
70 | t.TChar(ch="guang",rom="guang",notes=[t.TNote(pitch=62,on=75,off=100)],ipa=None),
71 | t.TChar(ch=None,rom=None,notes=None,ipa=None),
72 | ]
73 | a=run("man",150,chars)
74 | #print(a[2].ipa[3])
75 |
76 | if(__name__=="__main__"):
77 | main()
--------------------------------------------------------------------------------
/vogen/synth/timetable.py:
--------------------------------------------------------------------------------
1 | """vogen.synth基础类库"""
2 | import datetime
3 | from vogen.synth import utils
4 | from typing import List,Optional
5 |
6 |
7 | def timeToFrame(timeSpan:datetime.timedelta)->float:
8 | return timeSpan/utils.Params.hopSize
9 | def frameToTime(frames:float)->datetime.timedelta:
10 | return frames*utils.Params.hopSize
11 |
12 | import json
13 |
14 | class TPhoneme():
15 | """
16 | ph:音素名称
17 | on:开始时间,单位为0.25s,取整
18 | off:结束时间,单位为0.25s,取整
19 | """
20 | def __init__(self,ph:str="",on:int=0,off:int=0):
21 | self.ph=ph
22 | self.on=on
23 | self.off=off
24 |
25 | class TNote():
26 | """
27 | pitch:音高
28 | on:开始时间,单位为0.25s,取整
29 | off:结束时间,单位为0.25s,取整
30 | """
31 | def __init__(self,pitch:int="",on:int=0,off:int=0):
32 | self.pitch=pitch
33 | self.on=on
34 | self.off=off
35 |
36 | class TChar():
37 | """
38 | ch:汉字,对于空白为None
39 | rom:拼音,对于空白为None
40 | notes:音高列表
41 | ipa:(待处理)音素列表
42 | """
43 | def __init__(self,
44 | ch:Optional[str]=None,
45 | rom:Optional[str]=None,
46 | notes:Optional[List[TNote]]=None,
47 | ipa:Optional[List[TPhoneme]]=None):
48 | self.ch=ch
49 | self.rom=rom
50 | self.notes=notes
51 | self.ipa=ipa
52 |
53 | class TUtt():
54 | def __init__(self,uttStartSec:float=0.0,uttDur:int=0,romScheme:str="",chars:List[TChar]=[]):
55 | self.uttStartSec=uttStartSec
56 | self.uttDur=uttDur
57 | self.romScheme=romScheme
58 | self.chars=chars
--------------------------------------------------------------------------------
/vogen/synth/utils.py:
--------------------------------------------------------------------------------
1 | """常量及通用功能代码"""
2 | import datetime
3 | import onnxruntime
4 | from typing import Dict
5 |
6 | class Params():
7 | fs=44100
8 | channels=1
9 | worldFftSize=2048
10 |
11 | hopSize=datetime.timedelta(milliseconds=10)
12 | headSil=datetime.timedelta(seconds=0.5)
13 | tailsil=datetime.timedelta(seconds=0.5)
14 |
15 | #let appDir=
16 |
17 | #模型导入与缓存
18 | class ModelManager():
19 | def __init__(self,pathtemplate:str):
20 | self.models={}
21 | self.pathtemplate=pathtemplate
22 |
23 | def get(self,id:str):
24 | if(not id in self.models):
25 | self.models[id]=onnxruntime.InferenceSession(self.pathtemplate.format(id))
26 | return self.models[id]
27 |
--------------------------------------------------------------------------------