├── .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 | 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 | 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 | --------------------------------------------------------------------------------