Compare commits

...

446 Commits

Author SHA1 Message Date
github-actions[bot]
d9d890975a
@mallocfree009 has signed the CLA from Pull Request #1504 2025-05-17 10:32:01 +00:00
wok
8043fce1ce READMEファイルを更新し、v.2.0.78-betaの新機能とバグ修正を追加しました。 2025-05-16 01:33:01 +09:00
wok
3677f6e268 RTX 5090に関する新機能を追加し、各言語のREADMEファイルを更新しました。 2025-05-03 04:06:57 +09:00
wok
0318700981 update 2025-02-16 01:26:08 +09:00
wok
66cbbeed1a update 2024-11-15 04:10:35 +09:00
wok
b262d28c10 update 2024-11-13 02:01:48 +09:00
wok
38a9164e5c update 2024-11-08 23:26:14 +09:00
wok
e472934bb4 update 2024-11-08 12:34:18 +09:00
wok
6129780229 fix typo 2024-10-08 20:10:41 +09:00
wok
e821960c59 Merge branch 'master' of github.com:w-okada/voice-changer 2024-10-08 14:54:33 +09:00
wok
fa77d69bed update 2024-10-08 14:54:30 +09:00
w-okada
7ab6a63a67
Merge pull request #1347 from QweRezOn/master
Add Russian Readme File
2024-09-15 08:01:23 +09:00
github-actions[bot]
763a6a0763
@QweRezOn has signed the CLA from Pull Request #1347 2024-09-13 17:04:56 +00:00
QweRez
dfbc95bd61
Update README_ru.md 2024-09-13 20:03:50 +03:00
QweRez
33387bd351
Update README.md 2024-09-13 20:02:44 +03:00
QweRez
b02c4f48c3
Create README_dev_ru.md 2024-09-13 20:02:19 +03:00
QweRez
006b9d575c
Update README_dev_en.md 2024-09-13 19:59:08 +03:00
QweRez
4ebcd670e7
Update README_en.md 2024-09-13 19:57:30 +03:00
QweRez
0b5daf162b
Create README_ru.md
add ru
2024-09-13 19:56:56 +03:00
wok
11b5deecb8 update 2024-08-27 09:29:07 +09:00
wok
fd849db239 update 2024-08-21 10:29:31 +09:00
wok
6d9e735883 update 2024-08-18 23:13:17 +09:00
wok
b5d3e5f066 update 2024-08-07 19:51:20 +09:00
wok
a75f87e433 update 2024-08-06 23:47:11 +09:00
wok
285615d67c update 2024-08-01 11:01:20 +09:00
wok
eef8395205 update 2024-07-27 18:14:50 +09:00
wok
465ab1ff23 update 2024-07-21 02:29:03 +09:00
wok
1f51581ae3 update 2024-07-20 05:37:14 +09:00
wok
87b547e724 update 2024-07-20 02:32:21 +09:00
wok
3b83221cec update 2024-07-20 02:30:06 +09:00
wok
f79855f8b2 update 2024-07-10 23:54:40 +09:00
wok
1952c76533 update 2024-06-30 17:07:52 +09:00
wok
92f0b1aaf5 update 2024-06-30 16:17:10 +09:00
wok
ebea9d2692 update 2024-06-29 07:07:58 +09:00
wok
a91ef76b64 update 2024-06-29 07:06:55 +09:00
wok
0cd7f69931 update 2024-06-29 07:05:57 +09:00
wok
b350812083 update 2024-06-29 07:05:30 +09:00
wok
80ccc0b1d7 update 2024-06-29 07:03:40 +09:00
wok
cc60c7adfb update 2024-06-29 07:03:11 +09:00
wok
d61f6b8e99 update 2024-06-29 07:02:35 +09:00
wok
7adc1f1cf5 update 2024-06-29 07:02:04 +09:00
wok
7e177ee84c update 2024-06-29 07:01:26 +09:00
wok
51046638d6 update 2024-06-29 07:00:57 +09:00
wok
2522d44f13 update 2024-06-29 07:00:30 +09:00
wok
018cab3ded update 2024-06-29 07:00:01 +09:00
wok
a1714878a7 update 2024-06-29 06:59:34 +09:00
wok
23b69ba121 update 2024-06-29 06:56:19 +09:00
wok
9f6903e4e9 update 2024-06-29 06:48:05 +09:00
wok
4c59ab5431 update 2024-06-24 03:49:37 +09:00
wok
33d74e8e73 Merge branch 'master' of github.com:w-okada/voice-changer 2024-06-24 03:47:52 +09:00
wok
5f1ca7af51 update 2024-06-24 03:47:25 +09:00
github-actions[bot]
56a5094881
@Nick088Official has signed the CLA from Pull Request #1241 2024-06-15 16:27:47 +00:00
wok
cde810a9d0 add cuda question 2024-06-12 05:01:52 +09:00
wok
73bb47f745 update 2024-06-10 20:09:30 +09:00
wok
349d268189 update 2024-06-05 18:39:35 +09:00
wok
3a8cbb07de update 2024-06-03 20:57:28 +09:00
github-actions[bot]
800285f2cd
@vitaliylag has signed the CLA from Pull Request #1224 2024-06-01 03:14:09 +00:00
github-actions[bot]
d3add2561d
@mrs1669 has signed the CLA from Pull Request #1171 2024-04-04 10:53:26 +00:00
w-okada
621ad25a8a
Merge pull request #1153 from deiteris/harden-security
Harden web server security
2024-04-02 16:04:02 +09:00
Yury
8dd8d7127d Refactor and add origin check to SIO 2024-03-18 22:52:46 +02:00
Yury
ce9b599501 Improve allowed origins input and use set 2024-03-17 16:26:55 +02:00
github-actions[bot]
28fc541891
@deiteris has signed the CLA from Pull Request #1153 2024-03-16 22:24:48 +00:00
Yury
cf2b693334 Harden web server security 2024-03-17 00:11:16 +02:00
w-okada
11672e9653 Merge branch 'master' of github.com:w-okada/voice-changer 2024-03-05 23:47:48 +09:00
w-okada
a42051bb40 update 2024-03-05 23:45:46 +09:00
w-okada
aa620e1cf0
Merge pull request #1141 from richardhbtz/patch-1
Misspelling "trouble"
2024-03-04 10:35:17 +09:00
w-okada
22bd9e3d7c Merge branch 'master' of github.com:w-okada/voice-changer 2024-03-04 10:33:56 +09:00
w-okada
6e774a1458 v.1.5.3.18 2024-03-04 10:33:16 +09:00
Richard Habitzreuter
0e2078a268
Misspelling "trouble" 2024-02-29 16:58:06 -03:00
github-actions[bot]
51233e0cbe
@brandonkovacs has signed the CLA from Pull Request #1137 2024-02-29 02:05:13 +00:00
w-okada
2ac5ec9feb update 2024-02-28 23:23:22 +09:00
w-okada
bc6e8a9c08 update 2024-02-28 23:08:49 +09:00
w-okada
39e0d0cfd6 update 2024-02-21 08:54:39 +09:00
w-okada
a1a3def686 Merge branch 'master' into v.1.5.3 2024-02-21 08:25:58 +09:00
w-okada
67804cad3c
Merge pull request #1092 from tg-develop/master
Bugfix FCPE
2024-02-21 08:25:25 +09:00
w-okada
ce8f843746 Merge branch 'master' into v.1.5.3 2024-02-21 08:22:36 +09:00
Tobias
0b954131b4 Bugfix FCPE 2024-01-21 14:02:35 +01:00
w-okada
927bba6467
Merge pull request #1077 from icecoins/master
implement of the fcpe in RVC
2024-01-18 06:32:12 +09:00
icecoins
8f230e5c45
Update FcpePitchExtractor.py 2024-01-12 02:28:17 +08:00
github-actions[bot]
41238258ba
@icecoins has signed the CLA from Pull Request #1077 2024-01-11 14:05:09 +00:00
icecoins
1cf9be54c7
undo modification 2024-01-11 22:02:36 +08:00
icecoins
303a15fef3
implement fcpe 2024-01-11 21:10:44 +08:00
icecoins
04f93b193f
implement fcpe 2024-01-11 21:09:57 +08:00
icecoins
fbf69cda19
implement fcpe 2024-01-11 21:08:47 +08:00
icecoins
8e42927880
implement fcpe 2024-01-11 21:07:38 +08:00
icecoins
4e254e42f7
implement fcpe 2024-01-11 21:07:03 +08:00
icecoins
cc72b93198
implement fcpe 2024-01-11 21:05:57 +08:00
icecoins
cc4783b85c
implement fcpe 2024-01-11 21:04:54 +08:00
icecoins
5fd31999e7
implement fcpe 2024-01-11 21:04:15 +08:00
icecoins
9f9e7016e2
Update GUI.json 2024-01-11 21:03:41 +08:00
icecoins
b96ba86be3
Update README.md 2024-01-11 21:00:50 +08:00
icecoins
98ee26e353
Update README.md 2024-01-11 20:58:49 +08:00
icecoins
e8244d61b7
Update README.md 2024-01-11 20:57:50 +08:00
github-actions[bot]
87d2382828
@sonphantrung has signed the CLA from Pull Request #1063 2024-01-04 08:20:51 +00:00
github-actions[bot]
03caf942b2
@Poleyn has signed the CLA from Pull Request #1057 2024-01-01 17:42:14 +00:00
w-okada
b215f3ba84 Modification:
- Timer update
  - Diffusion SVC Performance monitor
2023-12-21 04:11:25 +09:00
w-okada
0f0225cfcd update 2023-12-03 03:31:27 +09:00
w-okada
afb13bf976 bugfix: macos model_stati_dir 2023-12-03 02:50:51 +09:00
w-okada
06b8cf78d1 bugfix:
- clear setting
improve
  - file sanitizer
chage:
  - default input chunk size: 192.
    - decided by this chart.(https://rentry.co/VoiceChangerGuide#gpu-chart-for-known-working-chunkextra)
2023-12-03 02:02:28 +09:00
w-okada
c2b979a05f Merge branch 'master' of github.com:w-okada/voice-changer 2023-11-29 06:07:18 +09:00
w-okada
4a967c0b80 update 2023-11-29 05:42:58 +09:00
w-okada
ceb7d88cd9 update 2023-11-29 05:18:53 +09:00
w-okada
17597fdaab Add chihaya_jinja_sample
Web Edition improvement(16k test)

bugfix:
- merge slot
- servermode append error
2023-11-29 00:30:52 +09:00
w-okada
702d468d2f
Merge pull request #896 from hinabl/master
Added Auto Sampling Rate
2023-11-27 11:59:57 +09:00
Hina
f3d19fe95f
Removed Test dir
Finished test
2023-11-27 10:35:21 +08:00
Hina
69e81c4587 Added "some" weights.gg support on upload cell 2023-11-27 10:33:32 +08:00
github-actions[bot]
6ab743a2e2
@shdancer has signed the CLA from Pull Request #1017 2023-11-24 07:26:00 +00:00
w-okada
b24c781a72 async internal process 2023-11-23 16:44:23 +09:00
Hina
be3eb4033d Added Audio Notification 2023-11-23 15:43:02 +08:00
w-okada
0e7c0daebc refactor webedition flag 2023-11-23 08:10:29 +09:00
w-okada
3aa86f1e5a update 2023-11-23 07:54:35 +09:00
w-okada
08b3f25f0b Improve Device Detection 2023-11-23 07:53:14 +09:00
w-okada
ecf1976837 Standard Edtion avoid load process.js 2023-11-23 06:46:12 +09:00
w-okada
6fd61b9591 improve web edition gui 2023-11-23 06:20:54 +09:00
Hina
a216d4bc9d Kaggle Notebook | Public W-okada Voice Changer . | Version 2 2023-11-22 12:25:21 +08:00
Hina
935d817f6f Moved To new Link 2023-11-22 12:23:31 +08:00
w-okada
b895bdec4f WEB Edition icon bugfix 2023-11-22 10:06:38 +09:00
w-okada
a5c665c275 update 2023-11-22 07:10:44 +09:00
w-okada
82de23bb1a WIP:WebEdition GUI Improve 2023-11-22 07:10:34 +09:00
w-okada
ab837561d9 WIP:WEB version control 2023-11-22 05:53:15 +09:00
w-okada
14c73a71d2 ・モデルキャッシュ対応
・upkey対応
・モデル一覧追加
2023-11-21 23:10:43 +09:00
Hina
3e39b99ed7 Made Upload By Link smaller 2023-11-21 21:11:46 +08:00
Hina
df49eac1da Added Scuffed weights.gg model import (only works with model upload to huggingface) 2023-11-21 19:25:56 +08:00
w-okada
b8640f1f5a tooltip z-index 2023-11-21 11:45:38 +09:00
w-okada
db9e02cf09 add warmup progress 2023-11-21 11:45:27 +09:00
w-okada
f529331698
Merge pull request #1006 from tg-develop/AMD-Linux-Setup
Added guide for AMD Linux Setup
2023-11-21 04:03:11 +09:00
w-okada
3186e4322b Merge branch 'master' of github.com:w-okada/voice-changer 2023-11-20 07:43:53 +09:00
w-okada
6bda815669 WIP LLVC 2023-11-20 07:42:07 +09:00
w-okada
c2b031efa5 v.1.5.3.17 2023-11-20 05:38:46 +09:00
w-okada
d44119f9bf Beatrice Speaker graph
WIP: Web version
2023-11-19 22:28:38 +09:00
w-okada
079043ff6a beatrice speaker graph 2023-11-19 20:20:48 +09:00
github-actions[bot]
b6e743f032
@tg-develop has signed the CLA from Pull Request #1006 2023-11-18 10:51:21 +00:00
tg-develop
818f2470e3 Added guide for AMD Linux Setup 2023-11-18 11:49:31 +01:00
Hina
f36138d64e Update 2023-11-14 17:45:58 +08:00
Hina
1da0717a45 Fixed Kaggle Realtime VoiceChanger 2023-11-14 17:05:24 +08:00
w-okada
f15289ad98 WIP:WEBver 2023-11-14 08:17:27 +09:00
w-okada
958b03bd5a bugfix: timer update 2023-11-14 05:39:07 +09:00
Hina
218872ba75
Make Kaggle Realtime Voice Changer 2023-11-13 19:59:42 +08:00
Hina
53ea4cef57 Cleaned Some Cells 2023-11-13 01:25:20 +08:00
w-okada
85dfaff25f bugfix rest 32bit -> 16bit 2023-11-13 00:38:43 +09:00
w-okada
dadab1ad13 Experimental LLVC 2023-11-12 23:10:58 +09:00
w-okada
1e68e01e39 add handling setSinkID 2023-11-11 00:05:40 +09:00
Hina
da9e6aaf6b Removed "Under Construction" 2023-11-10 21:55:42 +08:00
w-okada
a35551906a update 2023-11-08 21:15:31 +09:00
w-okada
ca1cf9ed21 update 2023-11-08 21:11:22 +09:00
w-okada
b69496c0f3 update 2023-11-08 19:59:24 +09:00
w-okada
d03132d2ab bugfix: beatrice load 2023-11-08 19:54:13 +09:00
w-okada
3512bbb1eb Merge branch 'master' into v.1.5.3 2023-11-04 17:19:03 +09:00
w-okada
e563094714 update 2023-11-04 14:14:18 +09:00
w-okada
b32754134f update 2023-11-04 05:48:06 +09:00
w-okada
0bf927a43f update 2023-11-04 04:52:37 +09:00
w-okada
f6a3e98765 update 2023-11-04 04:46:19 +09:00
w-okada
4f7969176f update 2023-11-04 04:34:43 +09:00
w-okada
6272936e14 update 2023-11-04 04:29:28 +09:00
w-okada
2c8597d742 update 2023-11-04 04:22:24 +09:00
w-okada
56e26d438e WIP: Beatrice 2023-11-04 03:29:54 +09:00
w-okada
e62a140698 WIP:Internal version 2023-11-04 01:41:51 +09:00
Hina
ee827731b6 Merged Google Drive with Clone and install 2023-11-03 11:31:03 +08:00
Hina
5de0630bb4 Added Google Colab on version text 2023-11-03 11:01:48 +08:00
Hina
6d20b3dad2 Updated to Rafa's latest Voice Changer Colab 2023-11-03 10:17:57 +08:00
w-okada
b624999636
Merge pull request #978 from qlife1146/master
韓国語翻訳、誤字修正、内容追加
2023-11-03 10:27:39 +09:00
w-okada
4f534a2c44 update 2023-11-03 10:17:45 +09:00
Luca Park
eb30f9a4e1 韓国語翻訳、誤字修正 2023-11-03 03:41:16 +09:00
Hina
497c7c0678 Created using Colaboratory 2023-11-02 21:54:22 +08:00
w-okada
a3160c12af
Merge pull request #976 from qlife1146/master
韓国語翻訳追加
2023-11-02 10:52:19 +09:00
github-actions[bot]
59c80ca856
@qlife1146 has signed the CLA from Pull Request #976 2023-11-02 01:46:14 +00:00
Luca Park
23be190e21 파일 추가(U)
trouble_shoot_communication_ko.md
tutorial_rvc_ko_latest.md
tutorial_monitor_consept_ko.md

파일 제목 수정
tutorial_rvc_en_1_5_3_7.md -> tutorial_rvc_en_1_5_3_7.md
전각 숫자에서 반각 숫자로 수정
tutorial_device_mode.md -> tutorial_device_mode_ja.md
더 넓은 번역 제공을 위해 제목에 언어 추가

파일 내용 수정 1: 전부 tutorial_device_mode -> tutorial_device_mode_ja 링크 수정
tutorial_rvc_en_1_5_3_1.md
tutorial_rvc_en_1_5_3_3.md
tutorial_rvc_en_1_5_3_7.md
tutorial_rvc_ja_1_5_3_1.md
tutorial_rvc_ja_1_5_3_3.md
tutorial_rvc_ja_1_5_3_7.md

파일 내용 수정 2: 언어 추가
tutorial_rvc_en_latest.md
tutorial_rvc_ja_latest.md

파일 내용 수정 3: VCClient -> VC Client
tutorial_rvc_en_1_5_3_7.md
tutorial_rvc_en_latest.md
tutorial_device_mode_ja.md
tutorial_monitor_consept_ja.md
tutorial_rvc_ja_1_5_3_7.md
tutorial_rvc_ja_latest.md
trouble_shoot_communication_ja.md

파일 내용 수정 4: 맞춤법 claer -> clear
tutorial_rvc_en_latest.md
tutorial_rvc_ja_latest.md
2023-11-02 03:31:20 +09:00
Hina
f86bee6768 Fixed libportaudio missing 2023-11-01 22:18:42 +08:00
Hina
65cde67b49 Using Rafa's Install 2023-11-01 13:00:22 +08:00
Hina
5c84c4cb91 Removed packages from requirements that are not needed or already installed (first batch) 2023-10-30 19:49:54 +08:00
Hina
6094be47f2 Updated Credits and Info 2023-10-25 10:31:14 +08:00
w-okada
ba96930432
Update issue.yaml 2023-10-14 04:28:43 +09:00
Hina
c0db39990d Background WEEEE 2023-10-13 19:18:07 +08:00
w-okada
acc0130e7c update 2023-10-09 16:39:20 +09:00
w-okada
35e1fc38cd update 2023-10-09 14:40:31 +09:00
w-okada
9b39111f46 update rmvpe checkpoint 2023-10-09 14:24:56 +09:00
w-okada
70ba5d9f24 Merge branch 'master' into v.1.5.3 2023-10-09 12:15:55 +09:00
w-okada
782d003a91 update 2023-10-09 12:15:03 +09:00
w-okada
c834d181a6
Update question.yaml 2023-10-08 02:20:45 +09:00
w-okada
16873a9175
Update issue.yaml 2023-10-05 10:56:38 +09:00
w-okada
a13747b64f
Update issue.yaml 2023-10-05 10:55:55 +09:00
w-okada
2b9a497eb0 update 2023-10-05 10:53:58 +09:00
Hina
22b0f83992 Created using Colaboratory 2023-10-04 00:12:43 +08:00
Hina
ae52548113 Removed localtunnel 2023-10-03 16:48:48 +08:00
Hina
bfd7f5cef7 Created using Colaboratory 2023-09-29 00:55:14 +08:00
Hina
8d3a0f8c73 Created using Colaboratory 2023-09-29 00:44:13 +08:00
Hina
889874ecaf
Added Auto Sampling Rate
The Model Uploader Colab Cell is probably more buggy now but it works....
2023-09-28 19:18:21 +08:00
w-okada
cfe2e6afd1 add internal callback 2023-09-28 04:19:41 +09:00
w-okada
265705f087 get model info snipet 2023-09-27 02:25:09 +09:00
w-okada
b4b1001b70 separate rest client 2023-09-27 02:24:44 +09:00
w-okada
92ab1a627c
Merge pull request #892 from hinabl/master
Upload Model In Colab
2023-09-27 01:41:12 +09:00
github-actions[bot]
e3fafd5775
@hinabl has signed the CLA from Pull Request #892 2023-09-26 16:38:38 +00:00
Hina
5061113bdd
Update Hina_Modified_Realtime_Voice_Changer_on_Colab.ipynb 2023-09-25 19:54:08 +08:00
Hina
166b8adb62
Update Hina_Modified_Realtime_Voice_Changer_on_Colab.ipynb 2023-09-25 19:52:57 +08:00
Hina
abcfb93964
Update Hina_Modified_Realtime_Voice_Changer_on_Colab.ipynb 2023-09-25 19:50:50 +08:00
Hina
d5d6bd8910
Colab Version 2023-09-25 19:48:18 +08:00
w-okada
3ef54e979b apply code formatter 2023-09-25 13:25:07 +09:00
w-okada
acc44fb83f delete unused files 2023-09-25 11:23:57 +09:00
w-okada
d5e914634d delete unused files 2023-09-25 11:22:15 +09:00
w-okada
25cba16e5f Merge branch 'master' of github.com:w-okada/voice-changer 2023-09-23 03:28:43 +09:00
w-okada
1de21cde0c update 2023-09-23 03:19:01 +09:00
w-okada
0878f8f2f4 update 2023-09-23 01:23:57 +09:00
w-okada
61b725a853 add 16k(Experimental) 2023-09-23 00:55:19 +09:00
w-okada
ee6ba8ee90 disable unrecommended fodet on directml ver 2023-09-22 01:06:02 +09:00
w-okada
7febd37198 change default f0det to onnx_rmvpe 2023-09-22 00:50:09 +09:00
w-okada
654de69533 update 2023-09-21 09:28:41 +09:00
github-actions[bot]
051c59955b
@aeongdesu has signed the CLA from Pull Request #856 2023-09-16 12:13:12 +00:00
github-actions[bot]
66ebd8554a
@jonluca has signed the CLA from Pull Request #847 2023-09-14 21:10:32 +00:00
w-okada
4071115f7a
Merge pull request #844 from Rafacasari/master
Colab Updated + Kaggle Added
2023-09-14 19:40:07 +09:00
Rafa
f39a0831c5
Kaggle Update 2023-09-14 03:02:49 -03:00
Rafa
eb8cd25409
Kaggle Update 2023-09-14 03:01:27 -03:00
Rafa
074ac43acd
Create Kaggle_RealtimeVoiceChanger.ipynb 2023-09-14 02:59:50 -03:00
Rafa
8e56ecfcec
Update Realtime_Voice_Changer_on_Colab.ipynb 2023-09-14 02:50:45 -03:00
w-okada
71860e7b45
Merge pull request #832 from InsanEagle/master
Google Colab dependencies fix
2023-09-13 11:08:03 +09:00
w-okada
a4d41a6a07
Merge pull request #818 from Rafacasari/master
GITHUB: Added FAQ and re-work on Issues Template
2023-09-13 11:07:38 +09:00
github-actions[bot]
d60b492730
@InsanEagle has signed the CLA from Pull Request #832 2023-09-12 15:58:25 +00:00
InsanEagle
f191d1e029
Update Realtime_Voice_Changer_on_Colab.ipynb 2023-09-12 18:28:06 +03:00
InsanEagle
8d877b914a
Update Realtime_Voice_Changer_on_Colab.ipynb 2023-09-12 18:27:06 +03:00
InsanEagle
f7e3377d95
Update Realtime_Voice_Changer_on_Colab.ipynb 2023-09-12 17:20:41 +03:00
InsanEagle
dab44c804f
Update requirements.txt 2023-09-12 17:00:08 +03:00
InsanEagle
38fb8aa32f
Update requirements.txt 2023-09-12 16:47:19 +03:00
InsanEagle
3b5844ba10
Update requirements.txt 2023-09-12 16:43:58 +03:00
Rafa
0f2698fb6c
Update FAQ.md 2023-09-10 05:37:56 -03:00
Rafa
320ba88720
Update question.yaml 2023-09-10 05:00:12 -03:00
Rafa
30cbc75304
Update issue.yaml 2023-09-10 04:59:17 -03:00
Rafa
f0f3dad6c4
Update issue.yaml 2023-09-10 04:57:33 -03:00
Rafa
f750f17ceb
Update issue.yaml 2023-09-10 04:55:29 -03:00
Rafa
f64ce2b7b8
Update issue.yaml 2023-09-10 04:53:16 -03:00
Rafa
49ab9e80cb
Update issue.yaml 2023-09-10 04:49:45 -03:00
Rafa
ff22dd4b7c
Update issue.yaml 2023-09-10 04:44:50 -03:00
Rafa
e3bca2c49b
Update issue.yaml 2023-09-10 04:43:23 -03:00
Rafa
7df36f5a72
Update issue.yaml 2023-09-10 04:42:25 -03:00
Rafa
a2501c245f
Update issue.yaml 2023-09-10 04:40:14 -03:00
Rafa
8838ce757c
Update issue.yaml 2023-09-10 01:33:53 -03:00
Rafa
3fbc3f67d2
Update issue.yaml 2023-09-10 01:33:41 -03:00
Rafa
a8f4a71327
Update issue.yaml 2023-09-10 01:33:27 -03:00
Rafa
dd85f99545
Update issue.yaml 2023-09-10 01:32:59 -03:00
Rafa
cd857f4daa
Create FAQ.md 2023-09-10 01:30:57 -03:00
Rafa
6df24ad762
Update issue.yaml 2023-09-10 01:20:48 -03:00
Rafa
ba1823cad5
Update question.yaml 2023-09-10 01:20:21 -03:00
Rafa
26741a5ccc
Update issue.yaml 2023-09-10 01:20:03 -03:00
Rafa
afa1d60ed4
Update issue.yaml 2023-09-10 01:18:25 -03:00
Rafa
9784766ca2
Create question.yaml 2023-09-10 01:17:07 -03:00
Rafa
3a954f3dea
Update feature-request.yaml 2023-09-10 01:14:16 -03:00
Rafa
c594f279ae
Update feature-request.yaml 2023-09-10 01:13:09 -03:00
Rafa
ce6429bf3a
Update issue.yaml 2023-09-10 00:52:58 -03:00
Rafa
749e9540e3
Update feature-request.yaml 2023-09-10 00:52:27 -03:00
Rafa
5e82d3153a
Update feature-request.yaml 2023-09-10 00:49:21 -03:00
Rafa
7ea35044d0
Create feature-request.yaml 2023-09-10 00:45:09 -03:00
w-okada
6f8710fd50 improve onnx 2023-09-10 05:48:34 +09:00
w-okada
8e822c16e3 onnx improvement 2023-09-06 08:21:12 +09:00
w-okada
80a5ba91b8 WIP onnx improve 2023-09-06 08:04:39 +09:00
w-okada
93480636a3 update 2023-09-06 06:44:24 +09:00
w-okada
83543c20bc Merge branch 'master' into v.1.5.3 2023-09-05 18:53:07 +09:00
w-okada
eac95a1a5c
Merge pull request #789 from Rafacasari/master
new version of colab
2023-09-05 18:52:19 +09:00
Rafa
9e8ba81379
new version of colab 2023-09-03 20:44:22 -03:00
w-okada
219e10e4c9 update 2023-09-03 18:34:41 +09:00
w-okada
e736bcbbda update 2023-08-27 11:26:18 +09:00
w-okada
48d673227c update 2023-08-26 21:20:45 +09:00
w-okada
c4a6d0456a update 2023-08-26 20:50:23 +09:00
w-okada
1544c15627 update 2023-08-26 19:59:09 +09:00
w-okada
82caea452a update 2023-08-26 19:57:39 +09:00
w-okada
6a32c05c7e update 2023-08-26 19:50:06 +09:00
w-okada
7b0472bb64 update 2023-08-26 19:48:51 +09:00
w-okada
ea5cc27b2e bugfix pass through 2023-08-26 17:23:52 +09:00
w-okada
f40056bed3 Merge branch 'master' into v.1.5.3 2023-08-26 16:48:44 +09:00
w-okada
3ea542a01c Colaboratory を使用して作成しました 2023-08-26 16:48:00 +09:00
w-okada
0ecea3d2c4 Colaboratory を使用して作成しました 2023-08-26 16:47:11 +09:00
w-okada
35dc987657 Colaboratory を使用して作成しました 2023-08-26 16:41:58 +09:00
w-okada
c3d0cddecf Merge branch 'master' into v.1.5.3 2023-08-26 16:40:28 +09:00
w-okada
66c4d8f076 Colaboratory を使用して作成しました 2023-08-26 16:40:09 +09:00
w-okada
d9c659de0e Colaboratory を使用して作成しました 2023-08-26 16:39:24 +09:00
w-okada
4fe21c7f34 Merge branch 'master' into v.1.5.3 2023-08-26 16:36:54 +09:00
w-okada
71e91be293 Colaboratory を使用して作成しました 2023-08-26 16:32:39 +09:00
w-okada
43920fda4c handle pipeline initialization 2023-08-26 16:01:30 +09:00
w-okada
fa6944ce9e update 2023-08-26 15:11:04 +09:00
w-okada
8312189942 exp: update docker_folder 2023-08-26 14:40:30 +09:00
w-okada
9c89b53626 overwrite model 2023-08-26 14:18:54 +09:00
w-okada
78af3b7fff rmvpe 2023-08-26 13:48:03 +09:00
w-okada
8974bf78d2 skip pass through confirmation 2023-08-26 12:32:12 +09:00
w-okada
e8e339bd7e log update 2023-08-14 11:23:53 +09:00
w-okada
786c88cd06 update log control 2023-08-14 11:08:31 +09:00
w-okada
db3b079a97 update timer 2023-08-13 19:29:07 +09:00
w-okada
9fe14f94e2 reactor 2023-08-13 18:55:50 +09:00
w-okada
46ff05b04f Colaboratory を使用して作成しました 2023-08-11 02:05:06 +09:00
w-okada
4a98b80cbf
Merge pull request #683 from Rafacasari/master
Google Colab Support
2023-08-11 01:52:49 +09:00
w-okada
c4e73cee95
Merge pull request #674 from Eidenz/master
Update docker build to v.1.5.3.12
2023-08-11 01:52:05 +09:00
Rafa
7d28bb575c
i'm dumb 2023-08-10 09:18:36 -03:00
Rafa
a9dcefc772
Update 2023-08-10 08:46:16 -03:00
github-actions[bot]
26fdca22ae
@Rafacasari has signed the CLA from Pull Request #683 2023-08-10 11:43:35 +00:00
Rafa
183b109844
Google Colab Support 2023-08-10 08:41:27 -03:00
w-okada
b96a996071 update 2023-08-10 17:36:52 +09:00
w-okada
1132ba1ac0 bugfix: unknwon lib 2023-08-09 19:37:04 +09:00
w-okada
af49a25a9c Merge branch 'master' of github.com:w-okada/voice-changer 2023-08-09 19:24:54 +09:00
w-okada
6ceb514b19 update 2023-08-09 19:23:52 +09:00
github-actions[bot]
3223b1ab27
@Eidenz has signed the CLA from Pull Request #674 2023-08-09 09:45:39 +00:00
Eidenz
addd60a2f8
update(Dockerfile) upgrade to v.1.5.3.12 2023-08-09 11:42:04 +02:00
Eidenz
11219c11f6
Merge branch 'w-okada:master' into master 2023-08-09 11:41:11 +02:00
w-okada
6aa06851d3 bugfix: server mode change chunk
cause tune change
2023-08-09 18:22:18 +09:00
w-okada
19e664cc8d Bugfix: blank icon 2023-08-09 17:20:22 +09:00
w-okada
638619b3b4 pass through dialog 2023-08-09 17:11:03 +09:00
w-okada
50f963ff6b Beatrice 2023-08-09 16:55:59 +09:00
w-okada
5b43daa705
Merge pull request #661 from w-okada/v.1.5.3
Merge pull request #659 from w-okada/master
2023-08-07 20:23:10 +09:00
github-actions[bot]
fbb88ab1f3
@w-okada has signed the CLA from Pull Request #661 2023-08-07 11:22:40 +00:00
github-actions[bot]
dc523737e8
Creating file for storing CLA Signatures 2023-08-07 11:21:18 +00:00
w-okada
e382052366 update 2023-08-07 20:20:01 +09:00
w-okada
b66f793bc9 update 2023-08-07 20:18:39 +09:00
w-okada
04902c4dcb update 2023-08-07 20:17:03 +09:00
w-okada
b5d413fcda update 2023-08-07 20:12:20 +09:00
w-okada
8b27cba4ba
Merge pull request #659 from w-okada/master
tess
2023-08-07 20:08:14 +09:00
github-actions[bot]
f65d9dc439
@w-okada has signed the CLA from Pull Request #659 2023-08-07 11:07:14 +00:00
w-okada
e33958cfda
Merge pull request #658 from w-okada/v.1.5.3
update
2023-08-07 20:05:15 +09:00
w-okada
932037a841 update 2023-08-07 20:01:45 +09:00
github-actions[bot]
3333c4d127
Creating file for storing CLA Signatures 2023-08-07 11:01:35 +00:00
w-okada
e741ce9906 update 2023-08-07 20:01:16 +09:00
w-okada
14ed0a8db6 update 2023-08-07 19:59:14 +09:00
w-okada
c53ea6d50b update 2023-08-07 19:58:45 +09:00
w-okada
d1f9a2d5bc update 2023-08-07 19:54:54 +09:00
w-okada
df2406bae8 update 2023-08-07 19:54:12 +09:00
w-okada
23efe5fd11 update 2023-08-07 19:36:20 +09:00
w-okada
592235f312 update 2023-08-07 19:34:38 +09:00
w-okada
a4b7bfe479 update 2023-08-07 19:24:23 +09:00
w-okada
cc0917dab3 add cla assitantce 2023-08-07 19:16:08 +09:00
w-okada
ccdc98f9c2 add license 2023-08-07 10:59:39 +09:00
w-okada
56944ad594 improve gui 2023-08-06 09:57:58 +09:00
w-okada
1ba260e9b8 update 2023-08-06 09:37:20 +09:00
w-okada
9a3e730ec5 remove unused comment 2023-08-06 07:56:37 +09:00
w-okada
fa0c0cbceb Feature: pass through 2023-08-06 07:44:20 +09:00
w-okada
ff21c8130e bugfix: diffusionsvc sample 2023-08-06 07:09:32 +09:00
w-okada
38d7ec8c4f update 2023-08-06 05:46:13 +09:00
w-okada
3e78db4318 update 2023-08-06 05:43:36 +09:00
w-okada
e18138b5d6 skipdiffusion 2023-08-06 04:50:42 +09:00
w-okada
6d4c138821 remove slotindex from json 2023-08-05 13:24:11 +09:00
w-okada
db97441380 remove slotindex from json 2023-08-05 12:33:31 +09:00
w-okada
c08099a512 refactor: remove filename from merge req 2023-08-05 03:05:54 +09:00
w-okada
aef9d2c34b bugfix: merge model 2023-08-05 03:02:43 +09:00
w-okada
8b5eb32047 bugfix: 200slot gui 2023-08-04 12:20:04 +09:00
w-okada
2777896bb0 update slot design 2023-08-04 11:09:15 +09:00
w-okada
55f9604ba1 update 2023-08-04 07:26:46 +09:00
w-okada
7b82f021d8 change localhost -> 127.0.0.1 2023-08-04 06:53:57 +09:00
w-okada
7ac8a9497d update 2023-08-04 04:39:17 +09:00
w-okada
5dc66d1156 bugfix: not in cache data(gain) 2023-08-03 18:50:14 +09:00
w-okada
1ef2fd543d improve: cli default 2023-08-03 10:46:25 +09:00
w-okada
c9e994c3b6 improve: amd gpu list 2023-08-03 10:21:42 +09:00
w-okada
51906ba4f1 improve: separate gain between out/monitor 2023-08-03 09:56:32 +09:00
w-okada
0e878edc1e improve: add monitor to client mode 2023-08-03 05:15:05 +09:00
w-okada
2b08e47377 update 2023-08-03 03:45:44 +09:00
w-okada
4b2d3bb334 Merge branch 'master' into v.1.5.3 2023-08-03 03:44:45 +09:00
w-okada
b4555a6beb
Merge pull request #603 from JustSxm/master
Typo in EN "Chage Icon" -> "Change Icon"
2023-08-03 03:44:12 +09:00
w-okada
839755e82f improve: tune server device mode 2023-08-03 03:41:23 +09:00
w-okada
4abbe0839e update 2023-08-03 01:19:21 +09:00
JustSxm
778268d898
Typo 2023-07-30 16:04:56 -04:00
w-okada
fee9354ea6 update 2023-07-29 23:15:04 +09:00
w-okada
f48ad3755e update 2023-07-29 23:10:01 +09:00
w-okada
755511ed5c update 2023-07-29 07:35:11 +09:00
w-okada
09f9c00b02 update 2023-07-29 06:44:09 +09:00
w-okada
cdff7cc7f4 skip diffusion svc sample on mac 2023-07-29 05:21:06 +09:00
w-okada
6720fc5f08 disable voras on mac 2023-07-29 04:53:00 +09:00
w-okada
172e3f7a1d skip diffusionsvc on mac 2023-07-29 04:24:36 +09:00
w-okada
73203a13e3 increase slot size200 2023-07-28 01:05:54 +09:00
w-okada
b86e15a79f experimental: remove torch audio 2023-07-28 00:45:51 +09:00
w-okada
cacd127c76 Experimental: remve torchaudio from rvc 2023-07-28 00:41:19 +09:00
Eidenz
2a10d19cd5
Update Dockerfile 2023-07-27 12:22:14 +02:00
w-okada
3d2f5ad0da Experimental: eliminate torchaudio from rvc 2023-07-27 15:45:54 +09:00
w-okada
074a0a4530 update 2023-07-27 09:56:30 +09:00
w-okada
bf01337ce3 update 2023-07-27 09:56:20 +09:00
w-okada
347963c0f5 update 2023-07-27 08:49:52 +09:00
w-okada
bf742d86c1 update 2023-07-27 05:12:26 +09:00
w-okada
4e13ec3d6b update 2023-07-27 04:40:14 +09:00
w-okada
dd01e81e45 update 2023-07-27 04:38:54 +09:00
w-okada
9de74281f6 RMVPE:
different device bug, not finding root caused yet.
2023-07-27 04:12:58 +09:00
w-okada
f96b4c2414 logger, handle creating pipeline exception 2023-07-27 04:06:25 +09:00
w-okada
d2a2826a82 add log 2023-07-26 17:10:29 +09:00
w-okada
2e84fa7407 add logger 2023-07-26 17:04:55 +09:00
w-okada
3c54b6bd2b Bugfix: remove unefficient message 2023-07-25 22:59:41 +09:00
w-okada
8cd41a62a8 Bugfix: load sample rvc model 2023-07-24 22:31:05 +09:00
w-okada
98d104cbf2 update 2023-07-24 00:32:11 +09:00
w-okada
7200896940 improve: handle unhandled exception 2023-07-23 23:56:37 +09:00
w-okada
a8a392b20d bugfix: mps for rmvpe 2023-07-23 23:01:35 +09:00
w-okada
7ab7dba5c8 bugfix: initialize gpu 2023-07-23 21:19:45 +09:00
w-okada
f98d4a38e0 improve: launch sequence 2023-07-23 19:39:52 +09:00
w-okada
ff0a80362a update 2023-07-23 07:36:52 +09:00
w-okada
11756788aa improve:onnx export 2023-07-23 07:20:48 +09:00
w-okada
20e7a472be gui improvement 2023-07-22 13:23:43 +09:00
w-okada
acab6644f7 update 2023-07-22 12:18:04 +09:00
w-okada
ec4f62f54a
Merge pull request #514 from Eidenz/master
Update docker to v.1.5.3.10
2023-07-22 00:05:50 +09:00
Eidenz
5afe932126
update(start_docker.sh) added RMVPE 2023-07-21 16:58:00 +02:00
Eidenz
857ce5b90d
update(Dockerfile) version up and lock numpy 2023-07-21 16:57:07 +02:00
w-okada
6394314d25 update 2023-07-21 23:34:29 +09:00
w-okada
2ee83c7239 update 2023-07-21 23:33:32 +09:00
w-okada
3b2fcc9805 update 2023-07-21 22:02:33 +09:00
w-okada
c0448db0ec update 2023-07-21 21:05:16 +09:00
w-okada
d3e49efd69 add sample diffusion svc 2023-07-21 18:25:28 +09:00
w-okada
a0e890a438 update 2023-07-21 07:16:44 +09:00
w-okada
cc5d3305fe update 2023-07-19 22:42:04 +09:00
w-okada
a6b1f093e1 bugfinx: mps not support float64 2023-07-19 22:08:56 +09:00
w-okada
43a1583731 WIP: screen capture 2023-07-19 19:41:56 +09:00
w-okada
7e4eed04e6 TEST: screen capture2 2023-07-19 19:17:31 +09:00
w-okada
ba9b5de461 TEST: screen capture 2023-07-19 18:49:50 +09:00
w-okada
72702ee70a WIP: vocoder 2023-07-19 10:20:30 +09:00
w-okada
1d54687577 RVC: RMVPE support 2023-07-18 23:02:06 +09:00
w-okada
257d99e355 improve: directml device id 2023-07-18 09:43:17 +09:00
w-okada
87a10c8e11 Improve: Change inefficient processes 2023-07-18 08:36:21 +09:00
w-okada
0957fbc923 bugfix:
diffusion svc server mode silence front value
2023-07-18 08:29:17 +09:00
w-okada
b559582dc4 bugfix WIP: server device mode 2023-07-17 22:21:58 +09:00
w-okada
371e1b8cac Diffusion SVC:
pitch extractor sr is changed from fixed(16k) to audio sampl rate
2023-07-17 21:03:53 +09:00
w-okada
e8aeb1eaa7 update dev start command 2023-07-17 09:21:35 +09:00
w-okada
b133364b77 remove unused comment 2023-07-17 09:18:29 +09:00
w-okada
e6a3ae241d add DiffSinger Community Vocoders link 2023-07-17 09:10:31 +09:00
w-okada
022fda0aa6 WIP: diffusion svc model slot info 2023-07-17 08:20:41 +09:00
w-okada
37468e3cc9 WIP: Diffusion svc refining: auto speaker id detect 2023-07-17 07:41:32 +09:00
w-okada
b9429c7655 WIP: diffusion svc refining 2023-07-17 07:21:06 +09:00
w-okada
9eef8fcb11 WIP: refactor 2023-07-15 23:39:02 +09:00
w-okada
33e156ef9a WIP: DiffusionSVC 2023-07-15 23:24:37 +09:00
w-okada
2c4503ade8 WIP:diffusion svc config 2023-07-15 18:35:11 +09:00
w-okada
01291dc4e3 WIP: configure diffusion svc 2023-07-15 10:01:42 +09:00
w-okada
3ffacaed97 WIP: RMVPE 2023-07-15 09:34:29 +09:00
w-okada
7d7702bb79 WIP: RMVPE 2023-07-15 09:17:19 +09:00
w-okada
333e66786d WIP: cprep onnx 2023-07-15 06:07:33 +09:00
w-okada
485a747d55 WIP:crepe 2023-07-15 05:57:20 +09:00
w-okada
8339e72ef5 WIP dio 2023-07-15 05:47:06 +09:00
w-okada
8a2df2dbb4 WIP: dio 2023-07-15 05:40:40 +09:00
w-okada
02eadeeaa3 WIP: pitch extractor 2023-07-15 05:30:07 +09:00
w-okada
6a09338af5 WIP: Pitch extractor refactoring 2023-07-15 04:53:38 +09:00
w-okada
a69c89255b WIP: Volume Extaractor torch 2023-07-15 04:45:27 +09:00
w-okada
a1db94c0af WIP: first communication confirmation 2023-07-14 13:54:08 +09:00
w-okada
fbabfed566 WIP: remove comments 2023-07-14 04:28:03 +09:00
w-okada
5bf1202215 WIP: diffusion svc rt badf0 2023-07-14 03:33:04 +09:00
w-okada
9c829ac91a import diffusion svc core 2023-07-13 00:59:48 +09:00
w-okada
ad013bf4d3 update 2023-07-10 04:05:10 +09:00
w-okada
4c9e61bb35 update 2023-07-10 03:46:19 +09:00
w-okada
852b3c021c update docker id 2023-07-10 03:25:55 +09:00
w-okada
ae3587a615 update docker 2023-07-10 03:18:51 +09:00
w-okada
e897512ee1 Improvement:
- handling unknown model slot
2023-07-10 02:27:51 +09:00
w-okada
f55f1f2938 update 2023-07-09 13:27:19 +09:00
w-okada
065e23d5e3 update 2023-07-09 08:42:28 +09:00
w-okada
728e7bcca1 improve:
MMVCv15: max chunksize for onnx
2023-07-09 05:19:30 +09:00
w-okada
514ae768b7 Merge branch 'master' into v.1.5.3 2023-07-09 04:38:28 +09:00
w-okada
cda99da5b4
Merge pull request #434 from nadare881/crepe_bugfix
delete padding
2023-07-09 04:37:49 +09:00
nadare
816798e8b5 delete padding 2023-07-09 03:36:37 +09:00
w-okada
01a916d2dd Improve:
- keep f0 detector
2023-07-09 02:35:06 +09:00
w-okada
aca23038bb bugfix: input media file recover old setting 2023-07-08 20:05:35 +09:00
w-okada
b1d361aaae update 2023-07-08 19:49:00 +09:00
w-okada
6fb00c7d91 Improve: show onnx device 2023-07-08 19:07:41 +09:00
w-okada
bde95029b3 Bugfix: RVC fallback 2023-07-08 19:02:44 +09:00
w-okada
130ae7e398 update 2023-07-08 17:18:28 +09:00
305 changed files with 42603 additions and 19278 deletions

24
.github/FAQ.md vendored Normal file
View File

@ -0,0 +1,24 @@
# Frequently Asked Questions
Please read this FAQ before asking or making a bug report.
### General fixes:
*Do all these steps so you can fix some issues.*
- Restart the application
- Go to your Windows %AppData% (Win + R, then put %appdata% and press Enter) and delete the "**voice-changer-native-client**" folder
- Extract your .zip to a new location and avoid folders with space or specials characters (also avoid long file paths)
- If you don't have a GPU or have a too old GPU, try using the [Colab Version](https://colab.research.google.com/github/w-okada/voice-changer/blob/master/Realtime_Voice_Changer_on_Colab.ipynb) instead
### 1. AMD GPU don't appear or not working
> Please download the **latest DirectML version**, use the **f0 det. rmvpe_onnx** and .ONNX models only! (.pth models do not work properly, use the "Export to ONNX" it can take a while)
### 2. NVidia GPU don't appear or not working
> Make sure that the [NVidia CUDA Toolkit](https://developer.nvidia.com/cuda-downloads) drivers are installed on your PC and up-to-date
### 3. High CPU usage
> Decrease your EXTRA value and put the index feature to 0
### 4. High Latency
> Decrease your chunk value until you find a good mix of quality and response time
### 5. I'm hearing my voice without changes
> Make sure to disable **passthru** mode

View File

@ -0,0 +1,22 @@
name: Feature Request
description: Do you have some feature request? Use this template
title: "[REQUEST]: "
body:
- type: markdown
attributes:
value: When creating a feature request, please be aware that **we do not guarantee that your idea will be implemented**. We are always working to make our software better, so please be pacient and do not put pressure on our devs.
- type: input
id: few-words
attributes:
label: In a few words, describe your idea
description: With a few words, briefly describe your idea
placeholder: ex. My idea is to implement rmvpe!
validations:
required: true
- type: textarea
id: request
attributes:
label: More information
description: If you have a complex idea, please use this field to describe it more, please provide enough information so we can understand and implement your idea
validations:
required: false

View File

@ -1,112 +1,66 @@
name: Issue
name: Issue or Bug Report for v.1.x.x.x
description: Please provide as much detail as possible to convey the history of your problem.
title: "[ISSUE]: "
body:
- type: dropdown
id: issue-type
- type: markdown
attributes:
label: Issue Type
description: What type of issue would you like to report?
multiple: true
options:
- Feature Request
- Documentation Feature Request
- Bug Report
- Question
- Others
validations:
required: true
value: Please read our [FAQ](https://github.com/w-okada/voice-changer/blob/master/.github/FAQ.md) before making a bug report!
- type: input
id: vc-client-version
attributes:
label: vc client version number
description: filename of you download(.zip)
label: Voice Changer Version
description: Downloaded File Name (.zip)
placeholder: MMVCServerSIO_xxx_yyyy-zzzz_v.x.x.x.x.zip
validations:
required: true
- type: input
id: OS
attributes:
label: OS
description: OS name and version. e.g. Windows 10, Ubuntu 20.04, if you use mac, M1 or Intel.(Intel is not supported) and version venture, monterey, big sur.
label: Operational System
description: e.g. Windows 10, Ubuntu 20.04, MacOS Venture, MacOS Monterey, etc...
placeholder: Windows 10
validations:
required: true
- type: input
id: GPU
attributes:
label: GPU
description: GPU. If you have no gpu, please input none.
description: If you have no gpu, please input none.
validations:
required: true
- type: dropdown
id: clear-setting
- type: checkboxes
id: checks
attributes:
label: Clear setting
description: Have you tried clear setting?
label: Read carefully and check the options
options:
- "no"
- "yes"
validations:
required: true
- type: dropdown
id: sample-model
attributes:
label: Sample model
description: Sample model work fine
options:
- "no"
- "yes"
validations:
required: true
- type: dropdown
id: input-chunk-num
attributes:
label: Input chunk num
description: Have you tried to change input chunk num?
options:
- "no"
- "yes"
validations:
required: true
- type: dropdown
id: wait-for-launch
attributes:
label: Wait for a while
description: If the GUI won't start up, wait a several minutes. Alternatively, have you excluded it from your virus-checking software (at your own risk)?
options:
- "The GUI successfully launched."
- "no"
- "yes"
validations:
required: true
- type: dropdown
id: read-tutorial
attributes:
label: read tutorial
description: Have you read the tutorial? https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_en_latest.md
options:
- "no"
- "yes"
validations:
required: true
- label: I've tried to Clear Settings
- label: Sample/Default Models are working
- label: I've tried to change the Chunk Size
- label: GUI was successfully launched
- label: I've read the [tutorial](https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_en_latest.md)
- label: I've tried to extract to another folder (or re-extract) the .zip file
- type: input
id: vc-type
attributes:
label: Voice Changer type
description: Which type of voice changer you use? e.g. MMVC v1.3, RVC
label: Model Type
description: MMVC, so-vits-rvc, RVC, DDSP-SVC
placeholder: RVC
validations:
required: true
- type: input
id: model-type
attributes:
label: Model type
description: List up the type of model you use? e.g. pyTorch, ONNX, f0, no f0
validations:
required: true
- type: textarea
id: issue
attributes:
label: Situation
description: Developers spend a lot of time developing new features and resolving issues. If you really want to get it solved, please provide as much reproducible information and logs as possible.
label: Issue Description
description: Please provide as much reproducible information and logs as possible
- type: textarea
id: capture
attributes:
label: Application Screenshot
description: Please provide a screenshot of your application so we can see your settings (you can paste or drag-n-drop)
- type: textarea
id: logs-on-terminal
attributes:
label: Logs on console
description: Copy and paste the log on your console here
validations:
required: true

View File

@ -0,0 +1,82 @@
name: Issue or Bug Report for v.2.x.x
description: Please provide as much detail as possible to convey the history of your problem.
title: "[ISSUE for v2]: "
body:
- type: markdown
attributes:
value: Please read our [FAQ](https://github.com/w-okada/voice-changer/blob/master/.github/FAQ.md) before making a bug report!
- type: input
id: vc-client-version
attributes:
label: Voice Changer Version
description: Downloaded File Name (.zip)
placeholder: vcclient_win_std_x.y.x.zip, vcclient_win_cuda_torch_cuda_x.y.x.zip, or so
validations:
required: true
- type: input
id: OS
attributes:
label: Operational System
description: e.g. Windows 10, Ubuntu 20.04, MacOS Venture, MacOS Monterey, etc...
placeholder: Windows 10
validations:
required: true
- type: input
id: GPU
attributes:
label: GPU
description: If you have no gpu, please input none.
validations:
required: true
- type: input
id: CUDA
attributes:
label: CUDA Version
description: If you have nvidia gpu, please input version of cuda. Otherwise, please input none.
validations:
required: true
- type: checkboxes
id: checks
attributes:
label: Read carefully and check the options
options:
- label: If you use win_cuda_torch_cuda edition, setup cuda? [see here](https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html#requirements)
- label: If you use win_cuda edition, setup cuda and cudnn? [see here](https://onnxruntime.ai/docs/execution-providers/CUDA-ExecutionProvider.html#requirements)
- label: If you use mac edition, client is not launched automatically. Use chrome to open application.?
- label: I've tried to change the Chunk Size
- label: I've tried to set the Index to zero
- label: I've read the [tutorial](https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_en_latest.md)
- label: I've tried to extract to another folder (or re-extract) the .zip file
- type: dropdown
id: sample-model-work
attributes:
label: Does pre-installed model work?
options:
- "No"
- "YES"
default: 0
- type: input
id: vc-type
attributes:
label: Model Type
description: MMVC, so-vits-rvc, RVC, DDSP-SVC
placeholder: RVC
validations:
required: true
- type: textarea
id: issue
attributes:
label: Issue Description
description: Please provide as much reproducible information and logs as possible
- type: textarea
id: capture
attributes:
label: Application Screenshot
description: Please provide a screenshot of your application so we can see your settings (you can paste or drag-n-drop)
- type: textarea
id: logs-on-terminal
attributes:
label: Logs on console
description: Copy and paste the log on your console here
validations:
required: true

15
.github/ISSUE_TEMPLATE/question.yaml vendored Normal file
View File

@ -0,0 +1,15 @@
情報なしの質問が来てしまうので、このフォームを一時的に無効にする。
name: Question or Other
description: Do you any question? Use this template
body:
- type: markdown
attributes:
value: We are always working to make our software better, so please be pacient and wait for a response
- type: textarea
id: question
attributes:
label: Description
description: What is your question? (or other non-related to bugs/feature-request)
validations:
required: true

36
.github/workflows/cla.yml vendored Normal file
View File

@ -0,0 +1,36 @@
name: "CLA Assistant"
on:
issue_comment:
types: [created]
pull_request_target:
types: [opened, closed, synchronize]
jobs:
CLAssistant:
runs-on: ubuntu-latest
steps:
- name: "CLA Assistant"
if: (github.event.comment.body == 'recheck' || github.event.comment.body == 'I have read the CLA Document and I hereby sign the CLA') || github.event_name == 'pull_request_target'
# Beta Release
uses: cla-assistant/github-action@v2.1.3-beta
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# the below token should have repo scope and must be manually added by you in the repository's secret
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
with:
path-to-signatures: "signatures/version1/cla.json"
path-to-document: "https://raw.githubusercontent.com/w-okada/voice-changer/master/LICENSE-CLA" # e.g. a CLA or a DCO document
# branch should not be protected
branch: "master"
#allowlist: user1,bot*
#below are the optional inputs - If the optional inputs are not given, then default values will be taken
#remote-organization-name: enter the remote organization name where the signatures should be stored (Default is storing the signatures in the same repository)
#remote-repository-name: enter the remote repository name where the signatures should be stored (Default is storing the signatures in the same repository)
#create-file-commit-message: 'For example: Creating file for storing CLA Signatures'
#signed-commit-message: 'For example: $contributorName has signed the CLA in #$pullRequestNo'
#custom-notsigned-prcomment: 'pull request comment with Introductory message to ask new contributors to sign'
#custom-pr-sign-comment: 'The signature to be committed in order to sign the CLA'
#custom-allsigned-prcomment: 'pull request comment when all contributors has signed, defaults to **CLA Assistant Lite bot** All Contributors have signed the CLA.'
#lock-pullrequest-aftermerge: false - if you don't want this bot to automatically lock the pull request after merging (default - true)
#use-dco-flag: true - If you are using DCO instead of CLA

17
.gitignore vendored
View File

@ -36,6 +36,11 @@ server/memo.md
client/lib/dist
client/lib/worklet/dist
client/demo/public/models
client/demo/public/models_
client/demo/dist/models
client/demo/dist_web
client/demo/src/001_provider/backup
# client/demo/dist/ # demo用に残す
docker/cudnn/
@ -52,11 +57,21 @@ server/samples_0003_t.json
server/samples_0003_o.json
server/samples_0003_t2.json
server/samples_0003_o2.json
server/samples_0003_d2.json
server/samples_0004_t.json
server/samples_0004_o.json
server/samples_0004_d.json
server/test_official_v1_v2.json
server/test_ddpn_v1_v2.json
server/vcclient.log
start_trainer.sh
# venv
venv/
beatrice_internal_api.cp310-win_amd64.pyd
108_average_110b_10.bin
server/model_dir_static/Beatrice-JVS

View File

@ -1,55 +0,0 @@
# Release Check List
## Run
- [ ] Anaconda on Linux
- [ ] Docker on Linux
- [ ] Anaconda on WSL2
- [ ] Docker on WSL2
- [ ] Colab simple
- [ ] Colab normal
- [ ] Windows exe
- [ ] Mac(M1)
## Doc
- [ ] Readme
- [ ] Wiki
- [ ] Zenn
# Memo
## Release Process
一通り開発が終わったと思ったら.
(1) Dockerを生成
```
npm run build:docker
npm run push:docker
```
Tagをメモ。
(2) start2.shを編集
メモしたTagを貼り付け。
```
bash start2.sh
```
(3) exeファイル作成
(3-1) Win
・環境変数にリリースバージョンを設定
・pipenv
(4) Readmeにリンクをはる
(5) Branch 解除。Tag化
```
git add ...
git commit -m "wip: releasing"
git push
git checkout - && git merge - && git push && git checkout -
git checkout -
git branch -d v.1...
git tag v.1...
git push origin v.1
git branch v.1....
git checkout v.1...
```
(6) Colabチェック

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,351 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/github/hinabl/voice-changer-colab/blob/master/Hina_Modified_Realtime_Voice_Changer_on_Colab.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"metadata": {
"id": "Lbbmx_Vjl0zo"
},
"source": [
"### w-okada's Voice Changer | **Google Colab**\n",
"\n",
"---\n",
"\n",
"##**READ ME - VERY IMPORTANT**\n",
"\n",
"This is an attempt to run [Realtime Voice Changer](https://github.com/w-okada/voice-changer) on Google Colab, still not perfect but is totally usable, you can use the following settings for better results:\n",
"\n",
"If you're using a index: `f0: RMVPE_ONNX | Chunk: 112 or higher | Extra: 8192`\\\n",
"If you're not using a index: `f0: RMVPE_ONNX | Chunk: 96 or higher | Extra: 16384`\\\n",
"**Don't forget to select your Colab GPU in the GPU field (<b>Tesla T4</b>, for free users)*\n",
"> Seems that PTH models performance better than ONNX for now, you can still try ONNX models and see if it satisfies you\n",
"\n",
"\n",
"*You can always [click here](https://rentry.co/VoiceChangerGuide#gpu-chart-for-known-working-chunkextra\n",
") to check if these settings are up-to-date*\n",
"<br><br>\n",
"\n",
"---\n",
"\n",
"###Always use Colab GPU (**VERY VERY VERY IMPORTANT!**)\n",
"You need to use a Colab GPU so the Voice Changer can work faster and better\\\n",
"Use the menu above and click on **Runtime** » **Change runtime** » **Hardware acceleration** to select a GPU (**T4 is the free one**)\n",
"\n",
"---\n",
"\n",
"<br>\n",
"\n",
"# **Credits and Support**\n",
"Realtime Voice Changer by [w-okada](https://github.com/w-okada)\\\n",
"Colab files updated by [rafacasari](https://github.com/Rafacasari)\\\n",
"Recommended settings by [Raven](https://github.com/ravencutie21)\\\n",
"Modified again by [Hina](https://huggingface.co/HinaBl)\n",
"\n",
"Need help? [AI Hub Discord](https://discord.gg/aihub) » ***#help-realtime-vc***\n",
"\n",
"---"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "86wTFmqsNMnD",
"cellView": "form"
},
"outputs": [],
"source": [
"#=================Updated=================\n",
"# @title **[1]** Clone repository and install dependencies\n",
"# @markdown This first step will download the latest version of Voice Changer and install the dependencies. **It can take some time to complete.**\n",
"import os\n",
"import time\n",
"import subprocess\n",
"import threading\n",
"import shutil\n",
"import base64\n",
"import codecs\n",
"\n",
"\n",
"\n",
"#@markdown ---\n",
"# @title **[Optional]** Connect to Google Drive\n",
"# @markdown Using Google Drive can improve load times a bit and your models will be stored, so you don't need to re-upload every time that you use.\n",
"\n",
"Use_Drive=False #@param {type:\"boolean\"}\n",
"\n",
"from google.colab import drive\n",
"\n",
"if Use_Drive==True:\n",
" if not os.path.exists('/content/drive'):\n",
" drive.mount('/content/drive')\n",
"\n",
" %cd /content/drive/MyDrive\n",
"\n",
"\n",
"externalgit=codecs.decode('uggcf://tvguho.pbz/j-bxnqn/ibvpr-punatre.tvg','rot_13')\n",
"rvctimer=codecs.decode('uggcf://tvguho.pbz/uvanoy/eipgvzre.tvg','rot_13')\n",
"pathloc=codecs.decode('ibvpr-punatre','rot_13')\n",
"\n",
"from IPython.display import clear_output, Javascript\n",
"\n",
"def update_timer_and_print():\n",
" global timer\n",
" while True:\n",
" hours, remainder = divmod(timer, 3600)\n",
" minutes, seconds = divmod(remainder, 60)\n",
" timer_str = f'{hours:02}:{minutes:02}:{seconds:02}'\n",
" print(f'\\rTimer: {timer_str}', end='', flush=True) # Print without a newline\n",
" time.sleep(1)\n",
" timer += 1\n",
"timer = 0\n",
"threading.Thread(target=update_timer_and_print, daemon=True).start()\n",
"\n",
"!pip install colorama --quiet\n",
"from colorama import Fore, Style\n",
"\n",
"print(f\"{Fore.CYAN}> Cloning the repository...{Style.RESET_ALL}\")\n",
"!git clone --depth 1 $externalgit &> /dev/null\n",
"print(f\"{Fore.GREEN}> Successfully cloned the repository!{Style.RESET_ALL}\")\n",
"%cd $pathloc/server/\n",
"\n",
"# Read the content of the file\n",
"file_path = '../client/demo/dist/assets/gui_settings/version.txt'\n",
"\n",
"with open(file_path, 'r') as file:\n",
" file_content = file.read()\n",
"\n",
"# Replace the specific text\n",
"text_to_replace = \"-.-.-.-\"\n",
"new_text = \"Google.Colab\" # New text to replace the specific text\n",
"\n",
"modified_content = file_content.replace(text_to_replace, new_text)\n",
"\n",
"# Write the modified content back to the file\n",
"with open(file_path, 'w') as file:\n",
" file.write(modified_content)\n",
"\n",
"print(f\"Text '{text_to_replace}' has been replaced with '{new_text}' in the file.\")\n",
"\n",
"print(f\"{Fore.CYAN}> Installing libportaudio2...{Style.RESET_ALL}\")\n",
"!apt-get -y install libportaudio2 -qq\n",
"\n",
"!sed -i '/torch==/d' requirements.txt\n",
"!sed -i '/torchaudio==/d' requirements.txt\n",
"!sed -i '/numpy==/d' requirements.txt\n",
"\n",
"\n",
"print(f\"{Fore.CYAN}> Installing pre-dependencies...{Style.RESET_ALL}\")\n",
"# Install dependencies that are missing from requirements.txt and pyngrok\n",
"!pip install faiss-gpu fairseq pyngrok --quiet\n",
"!pip install pyworld --no-build-isolation --quiet\n",
"# Install webstuff\n",
"import asyncio\n",
"import re\n",
"!pip install playwright\n",
"!playwright install\n",
"!playwright install-deps\n",
"!pip install nest_asyncio\n",
"from playwright.async_api import async_playwright\n",
"print(f\"{Fore.CYAN}> Installing dependencies from requirements.txt...{Style.RESET_ALL}\")\n",
"!pip install -r requirements.txt --quiet\n",
"clear_output()\n",
"print(f\"{Fore.GREEN}> Successfully installed all packages!{Style.RESET_ALL}\")"
]
},
{
"cell_type": "code",
"source": [
"#@title **[Optional]** Upload a voice model (Run this before running the Voice Changer)\n",
"import os\n",
"import json\n",
"from IPython.display import Image\n",
"import requests\n",
"\n",
"model_slot = \"0\" #@param ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '10', '11', '12', '13', '14', '15', '16', '17', '18', '19', '20', '21', '22', '23', '24', '25', '26', '27', '28', '29', '30', '31', '32', '33', '34', '35', '36', '37', '38', '39', '40', '41', '42', '43', '44', '45', '46', '47', '48', '49', '50', '51', '52', '53', '54', '55', '56', '57', '58', '59', '60', '61', '62', '63', '64', '65', '66', '67', '68', '69', '70', '71', '72', '73', '74', '75', '76', '77', '78', '79', '80', '81', '82', '83', '84', '85', '86', '87', '88', '89', '90', '91', '92', '93', '94', '95', '96', '97', '98', '99', '100', '101', '102', '103', '104', '105', '106', '107', '108', '109', '110', '111', '112', '113', '114', '115', '116', '117', '118', '119', '120', '121', '122', '123', '124', '125', '126', '127', '128', '129', '130', '131', '132', '133', '134', '135', '136', '137', '138', '139', '140', '141', '142', '143', '144', '145', '146', '147', '148', '149', '150', '151', '152', '153', '154', '155', '156', '157', '158', '159', '160', '161', '162', '163', '164', '165', '166', '167', '168', '169', '170', '171', '172', '173', '174', '175', '176', '177', '178', '179', '180', '181', '182', '183', '184', '185', '186', '187', '188', '189', '190', '191', '192', '193', '194', '195', '196', '197', '198', '199']\n",
"\n",
"!rm -rf model_dir/$model_slot\n",
"#@markdown **[Optional]** Add an icon to the model\n",
"icon_link = \"https://cdn.donmai.us/sample/12/57/__rin_penrose_idol_corp_drawn_by_juu_ame__sample-12579843de9487cf2db82058ba5e77d4.jpg\" #@param {type:\"string\"}\n",
"icon_link = '\"'+icon_link+'\"'\n",
"!mkdir model_dir\n",
"!mkdir model_dir/$model_slot\n",
"#@markdown Put your model's download link here `(must be a zip file)` only supports **weights.gg** & **huggingface.co**\n",
"model_link = \"https://huggingface.co/HinaBl/Rin-Penrose/resolve/main/RinPenrose600.zip?download=true\" #@param {type:\"string\"}\n",
"\n",
"if model_link.startswith(\"https://www.weights.gg\") or model_link.startswith(\"https://weights.gg\"):\n",
" weights_code = requests.get(\"https://pastebin.com/raw/ytHLr8h0\").text\n",
" exec(weights_code)\n",
"else:\n",
" model_link = model_link\n",
"\n",
"model_link = '\"'+model_link+'\"'\n",
"!curl -L $model_link > model.zip\n",
"\n",
"# Conditionally set the iconFile based on whether icon_link is empty\n",
"if icon_link:\n",
" iconFile = \"icon.png\"\n",
" !curl -L $icon_link > model_dir/$model_slot/icon.png\n",
"else:\n",
" iconFile = \"\"\n",
" print(\"icon_link is empty, so no icon file will be downloaded.\")\n",
"\n",
"!unzip model.zip -d model_dir/$model_slot\n",
"\n",
"!mv model_dir/$model_slot/*/* model_dir/$model_slot/\n",
"!rm -rf model_dir/$model_slot/*/\n",
"#@markdown **Model Voice Convertion Setting**\n",
"Tune = 12 #@param {type:\"slider\",min:-50,max:50,step:1}\n",
"Index = 0 #@param {type:\"slider\",min:0,max:1,step:0.1}\n",
"\n",
"param_link = \"\"\n",
"if param_link == \"\":\n",
" paramset = requests.get(\"https://pastebin.com/raw/SAKwUCt1\").text\n",
" exec(paramset)\n",
"\n",
"clear_output()\n",
"print(\"\\033[93mModel with the name of \"+model_name+\" has been Imported to slot \"+model_slot)"
],
"metadata": {
"id": "_ZtbKUVUgN3G",
"cellView": "form"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"id": "lLWQuUd7WW9U",
"cellView": "form"
},
"outputs": [],
"source": [
"\n",
"#=======================Updated=========================\n",
"\n",
"# @title Start Server **using ngrok**\n",
"# @markdown This cell will start the server, the first time that you run it will download the models, so it can take a while (~1-2 minutes)\n",
"\n",
"# @markdown ---\n",
"# @markdown You'll need a ngrok account, but <font color=green>**it's free**</font> and easy to create!\n",
"# @markdown ---\n",
"# @markdown **1** - Create a <font color=green>**free**</font> account at [ngrok](https://dashboard.ngrok.com/signup) or **login with Google/Github account**\\\n",
"# @markdown **2** - If you didn't logged in with Google/Github, you will need to **verify your e-mail**!\\\n",
"# @markdown **3** - Click [this link](https://dashboard.ngrok.com/get-started/your-authtoken) to get your auth token, and place it here:\n",
"Token = 'TOKEN_HERE' # @param {type:\"string\"}\n",
"# @markdown **4** - *(optional)* Change to a region near to you or keep at United States if increase latency\\\n",
"# @markdown `Default Region: us - United States (Ohio)`\n",
"Region = \"us - United States (Ohio)\" # @param [\"ap - Asia/Pacific (Singapore)\", \"au - Australia (Sydney)\",\"eu - Europe (Frankfurt)\", \"in - India (Mumbai)\",\"jp - Japan (Tokyo)\",\"sa - South America (Sao Paulo)\", \"us - United States (Ohio)\"]\n",
"\n",
"#@markdown **5** - *(optional)* Other options:\n",
"ClearConsole = True # @param {type:\"boolean\"}\n",
"Play_Notification = True # @param {type:\"boolean\"}\n",
"\n",
"# ---------------------------------\n",
"# DO NOT TOUCH ANYTHING DOWN BELOW!\n",
"# ---------------------------------\n",
"\n",
"%cd $pathloc/server/\n",
"\n",
"from pyngrok import conf, ngrok\n",
"MyConfig = conf.PyngrokConfig()\n",
"MyConfig.auth_token = Token\n",
"MyConfig.region = Region[0:2]\n",
"#conf.get_default().authtoken = Token\n",
"#conf.get_default().region = Region\n",
"conf.set_default(MyConfig);\n",
"\n",
"import subprocess, threading, time, socket, urllib.request\n",
"PORT = 8000\n",
"\n",
"from pyngrok import ngrok\n",
"ngrokConnection = ngrok.connect(PORT)\n",
"public_url = ngrokConnection.public_url\n",
"\n",
"from IPython.display import clear_output\n",
"from IPython.display import Audio, display\n",
"def play_notification_sound():\n",
" display(Audio(url='https://raw.githubusercontent.com/hinabl/rmvpe-ai-kaggle/main/custom/audios/notif.mp3', autoplay=True))\n",
"\n",
"\n",
"def wait_for_server():\n",
" while True:\n",
" time.sleep(0.5)\n",
" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
" result = sock.connect_ex(('127.0.0.1', PORT))\n",
" if result == 0:\n",
" break\n",
" sock.close()\n",
" if ClearConsole:\n",
" clear_output()\n",
" print(\"--------- SERVER READY! ---------\")\n",
" print(\"Your server is available at:\")\n",
" print(public_url)\n",
" print(\"---------------------------------\")\n",
" if Play_Notification==True:\n",
" play_notification_sound()\n",
"\n",
"threading.Thread(target=wait_for_server, daemon=True).start()\n",
"\n",
"mainpy=codecs.decode('ZZIPFreireFVB.cl','rot_13')\n",
"\n",
"!python3 $mainpy \\\n",
" -p {PORT} \\\n",
" --https False \\\n",
" --content_vec_500 pretrain/checkpoint_best_legacy_500.pt \\\n",
" --content_vec_500_onnx pretrain/content_vec_500.onnx \\\n",
" --content_vec_500_onnx_on true \\\n",
" --hubert_base pretrain/hubert_base.pt \\\n",
" --hubert_base_jp pretrain/rinna_hubert_base_jp.pt \\\n",
" --hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \\\n",
" --nsf_hifigan pretrain/nsf_hifigan/model \\\n",
" --crepe_onnx_full pretrain/crepe_onnx_full.onnx \\\n",
" --crepe_onnx_tiny pretrain/crepe_onnx_tiny.onnx \\\n",
" --rmvpe pretrain/rmvpe.pt \\\n",
" --model_dir model_dir \\\n",
" --samples samples.json\n",
"\n",
"ngrok.disconnect(ngrokConnection.public_url)"
]
},
{
"cell_type": "markdown",
"source": [
"![](https://i.pinimg.com/474x/de/72/9e/de729ecfa41b69901c42c82fff752414.jpg)\n",
"![](https://i.pinimg.com/474x/de/72/9e/de729ecfa41b69901c42c82fff752414.jpg)"
],
"metadata": {
"id": "2Uu1sTSwTc7q"
}
}
],
"metadata": {
"colab": {
"provenance": [],
"private_outputs": true,
"gpuType": "T4",
"include_colab_link": true
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
},
"accelerator": "GPU"
},
"nbformat": 4,
"nbformat_minor": 0
}

View File

@ -0,0 +1,99 @@
{
"metadata":{
"kernelspec":{
"language":"python",
"display_name":"Python 3",
"name":"python3"
},
"language_info":{
"name":"python",
"version":"3.10.12",
"mimetype":"text/x-python",
"codemirror_mode":{
"name":"ipython",
"version":3
},
"pygments_lexer":"ipython3",
"nbconvert_exporter":"python",
"file_extension":".py"
}
},
"nbformat_minor":4,
"nbformat":4,
"cells":[
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://www.kaggle.com/code/rafacasari/wokada-voice-changer\" target=\"_parent\"><img src=\"https://img.shields.io/badge/Open%20In%20Kaggle-035a7d?style=for-the-badge&logo=kaggle&logoColor=white\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type":"markdown",
"source":"### [w-okada's Voice Changer](https://github.com/w-okada/voice-changer) | **Kaggle**\n\n---\n\n## **⬇ VERY IMPORTANT ⬇**\n\nYou can use the following settings for better results:\n\nIf you're using a index: `f0: RMVPE_ONNX | Chunk: 112 or higher | Extra: 8192`<br>\nIf you're not using a index: `f0: RMVPE_ONNX | Chunk: 96 or higher | Extra: 16384`<br>\n**Don't forget to select a GPU in the GPU field, <b>NEVER</b> use CPU!\n> Seems that PTH models performance better than ONNX for now, you can still try ONNX models and see if it satisfies you\n\n\n*You can always [click here](https://github.com/YunaOneeChan/Voice-Changer-Settings) to check if these settings are up-to-date*\n\n---\n**Credits**<br>\nRealtime Voice Changer by [w-okada](https://github.com/w-okada)<br>\nNotebook files updated by [rafacasari](https://github.com/Rafacasari)<br>\nRecommended settings by [YunaOneeChan](https://github.com/YunaOneeChan)\n\n**Need help?** [AI Hub Discord](https://discord.gg/aihub) » ***#help-realtime-vc***\n\n---",
"metadata":{
"id":"Lbbmx_Vjl0zo"
}
},
{
"cell_type":"markdown",
"source":"# Kaggle Tutorial\nRunning this notebook can be a bit complicated.\\\nAfter created your Kaggle account, you'll need to **verify your phone number** to be able to use Internet Connection and GPUs.\\\nFollow the instructions on the image below.\n\n## <font color=blue>*You can use <b>GPU P100</b> instead of GPU T4, some people are telling that <b>P100 is better</b>.*</font>\n![instructions.png](https://i.imgur.com/0NutkD8.png)",
"metadata":{
}
},
{
"cell_type":"markdown",
"source":"# Clone repository and install dependencies\nThis first step will download the latest version of Voice Changer and install the dependencies. **It will take some time to complete.**",
"metadata":{
}
},
{
"cell_type":"code",
"source":"# This will make that we're on the right folder before installing\n%cd /kaggle/working/\n\n!pip install colorama --quiet\nfrom colorama import Fore, Style\nimport os\n\nprint(f\"{Fore.CYAN}> Cloning the repository...{Style.RESET_ALL}\")\n!git clone https://github.com/w-okada/voice-changer.git --quiet\nprint(f\"{Fore.GREEN}> Successfully cloned the repository!{Style.RESET_ALL}\")\n%cd voice-changer/server/\n\nprint(f\"{Fore.CYAN}> Installing libportaudio2...{Style.RESET_ALL}\")\n!apt-get -y install libportaudio2 -qq\n\nprint(f\"{Fore.CYAN}> Installing pre-dependencies...{Style.RESET_ALL}\")\n# Install dependencies that are missing from requirements.txt and pyngrok\n!pip install faiss-gpu fairseq pyngrok --quiet \n!pip install pyworld --no-build-isolation --quiet\nprint(f\"{Fore.CYAN}> Installing dependencies from requirements.txt...{Style.RESET_ALL}\")\n!pip install -r requirements.txt --quiet\n\n# Download the default settings ^-^\nif not os.path.exists(\"/kaggle/working/voice-changer/server/stored_setting.json\"):\n !wget -q https://gist.githubusercontent.com/Rafacasari/d820d945497a01112e1a9ba331cbad4f/raw/8e0a426c22688b05dd9c541648bceab27e422dd6/kaggle_setting.json -O /kaggle/working/voice-changer/server/stored_setting.json\nprint(f\"{Fore.GREEN}> Successfully installed all packages!{Style.RESET_ALL}\")\n\nprint(f\"{Fore.GREEN}> You can safely ignore the dependency conflict errors, it's a error from Kaggle and don't interfer on Voice Changer!{Style.RESET_ALL}\")",
"metadata":{
"id":"86wTFmqsNMnD",
"cellView":"form",
"_kg_hide-output":false,
"execution":{
"iopub.status.busy":"2023-09-14T04:01:17.308284Z",
"iopub.execute_input":"2023-09-14T04:01:17.308682Z",
"iopub.status.idle":"2023-09-14T04:08:08.475375Z",
"shell.execute_reply.started":"2023-09-14T04:01:17.308652Z",
"shell.execute_reply":"2023-09-14T04:08:08.473827Z"
},
"trusted":true
},
"execution_count":0,
"outputs":[
]
},
{
"cell_type":"markdown",
"source":"# Start Server **using ngrok**\nThis cell will start the server, the first time that you run it will download the models, so it can take a while (~1-2 minutes)\n\n---\nYou'll need a ngrok account, but <font color=green>**it's free**</font> and easy to create!\n---\n**1** - Create a **free** account at [ngrok](https://dashboard.ngrok.com/signup)\\\n**2** - If you didn't logged in with Google or Github, you will need to **verify your e-mail**!\\\n**3** - Click [this link](https://dashboard.ngrok.com/get-started/your-authtoken) to get your auth token, and replace **YOUR_TOKEN_HERE** with your token.\\\n**4** - *(optional)* Change to a region near to you",
"metadata":{
}
},
{
"cell_type":"code",
"source":"# ---------------------------------\n# SETTINGS\n# ---------------------------------\n\nToken = '2Tn2hbfLtw2ii6DHEJy7SsM1BjI_21G14MXSwz7qZSDL2Dv3B'\nClearConsole = True # Clear console after initialization. Set to False if you are having some error, then you will be able to report it.\nRegion = \"sa\" # Read the instructions below\n\n# You can change the region for a better latency, use only the abbreviation\n# Choose between this options: \n# us -> United States (Ohio)\n# ap -> Asia/Pacific (Singapore)\n# au -> Australia (Sydney)\n# eu -> Europe (Frankfurt)\n# in -> India (Mumbai)\n# jp -> Japan (Tokyo)\n# sa -> South America (Sao Paulo)\n\n# ---------------------------------\n# DO NOT TOUCH ANYTHING DOWN BELOW!\n# ---------------------------------\n\n%cd /kaggle/working/voice-changer/server\n \nfrom pyngrok import conf, ngrok\nMyConfig = conf.PyngrokConfig()\nMyConfig.auth_token = Token\nMyConfig.region = Region\n#conf.get_default().authtoken = Token\n#conf.get_default().region = Region\nconf.set_default(MyConfig);\n\nimport subprocess, threading, time, socket, urllib.request\nPORT = 8000\n\nfrom pyngrok import ngrok\nngrokConnection = ngrok.connect(PORT)\npublic_url = ngrokConnection.public_url\n\nfrom IPython.display import clear_output\n\ndef wait_for_server():\n while True:\n time.sleep(0.5)\n sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n result = sock.connect_ex(('127.0.0.1', PORT))\n if result == 0:\n break\n sock.close()\n if ClearConsole:\n clear_output()\n print(\"--------- SERVER READY! ---------\")\n print(\"Your server is available at:\")\n print(public_url)\n print(\"---------------------------------\")\n\nthreading.Thread(target=wait_for_server, daemon=True).start()\n\n!python3 MMVCServerSIO.py \\\n -p {PORT} \\\n --https False \\\n --content_vec_500 pretrain/checkpoint_best_legacy_500.pt \\\n --content_vec_500_onnx pretrain/content_vec_500.onnx \\\n --content_vec_500_onnx_on true \\\n --hubert_base pretrain/hubert_base.pt \\\n --hubert_base_jp pretrain/rinna_hubert_base_jp.pt \\\n --hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \\\n --nsf_hifigan pretrain/nsf_hifigan/model \\\n --crepe_onnx_full pretrain/crepe_onnx_full.onnx \\\n --crepe_onnx_tiny pretrain/crepe_onnx_tiny.onnx \\\n --rmvpe pretrain/rmvpe.pt \\\n --model_dir model_dir \\\n --samples samples.json\n\nngrok.disconnect(ngrokConnection.public_url)",
"metadata":{
"id":"lLWQuUd7WW9U",
"cellView":"form",
"_kg_hide-input":false,
"scrolled":true,
"trusted":true
},
"execution_count":null,
"outputs":[
]
}
]
}

70
LICENSE
View File

@ -20,7 +20,6 @@ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License
Copyright (c) 2022 Isle Tennos
@ -63,4 +62,71 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
SOFTWARE.
MIT License
Copyright (c) 2023 liujing04
Copyright (c) 2023 源文雨
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License
Copyright (c) 2023 yxlllc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
MIT License
Copyright (c) 2023 yxlllc
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

27
LICENSE-CLA Normal file
View File

@ -0,0 +1,27 @@
Contributor License Agreement
Copyright (c) 2022 Wataru Okada
本契約は、当社とあなた(以下、"貢献者"とします)の間で締結され、貢献者が当社に対してソフトウェアプロジェクト(以下、"プロジェクト"とします)に対する貢献(以下、"貢献"とします)を提供する際の条件を定めます。
1. 貢献者は、提供する貢献が、貢献者自身のオリジナルな作品であり、商標、著作権、特許、または他の知的財産権を侵害していないことを保証します。
2. 貢献者は、貢献を当社に対して無償で提供し、当社はそれを無制限に使用、複製、修正、公開、配布、サブライセンスを付与し、またその販売する権利を得ることに同意します。
3. 本契約が終了した場合でも、第 2 項で述べた権利は当社に留保されます。
4. 当社は貢献者の貢献を受け入れる義務を負わず、また貢献者に一切の補償をする義務を負わないことに貢献者は同意します。
5. 本契約は当社と貢献者双方の書面による合意により修正されることがあります。
"This Agreement is made between our Company and you (hereinafter referred to as "Contributor") and outlines the terms under which you provide your Contributions (hereinafter referred to as "Contributions") to our software project (hereinafter referred to as "Project").
1. You warrant that the Contributions you are providing are your original work and do not infringe any trademark, copyright, patent, or other intellectual property rights.
2. You agree to provide your Contributions to the Company for free, and the Company has the unlimited right to use, copy, modify, publish, distribute, and sublicense, and also sell the Contributions.
3. Even after the termination of this Agreement, the rights mentioned in the above clause will be retained by the Company.
4. The Company is under no obligation to accept your Contributions or to compensate you in any way for them, and you agree to this.
5. This Agreement may be modified by written agreement between the Company and the Contributor."

13
LICENSE-NOTICE Normal file
View File

@ -0,0 +1,13 @@
1. Diffusion SVC, DDSP SVC は vocodeer は DiffSinger Community Vocoders を使用しています。次のリンクからライセンスをご確認ください。
別のモデルを使用する場合は pretrain\\nsf_hifigan に設置してください。
https://openvpi.github.io/vocoders/
Diffusion SVC and DDSP SVC uses DiffSinger Community Vocoders. Please check the license from the following link.
Please place it on pretrain\\nsf_hifigan if you are using a different model.
https://openvpi.github.io/vocoders/
2. Beatrice JVS Corpus Edition のライセンスについてはこちらを確認してください。
[readme](https://github.com/w-okada/voice-changer/blob/master/server/voice_changer/Beatrice/)
Please check here for the license of the Beatrice JVS Corpus Edition.
[readme](https://github.com/w-okada/voice-changer/blob/master/server/voice_changer/Beatrice/)

View File

@ -1,334 +0,0 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
},
"gpuClass": "standard",
"accelerator": "GPU"
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/github/w-okada/voice-changer/blob/dev/MMVCTrainerFrontendDemo.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"Voice Changer Simple (デモ版)\n",
"---\n",
"\n",
"このートはVoice ChangerをColab上で動かすデモ版です。\n",
"\n",
"正式版はローカルPCのDocker上で動かすアプリケーションです。\n",
"\n",
"正式版は、多くの場合より少ないタイムラグで滑らかに音声を変換できます。\n",
"\n",
"詳細な使用方法はこちらの[リポジトリ](https://github.com/w-okada/voice-changer)からご確認ください。\n"
],
"metadata": {
"id": "Lbbmx_Vjl0zo"
}
},
{
"cell_type": "markdown",
"source": [
"# GPUを確認\n",
"GPUを用いたほうが高速に処理が行えます。\n",
"\n",
"下記のコマンドでGPUが確認できない場合は、上のメニューから\n",
"\n",
"「ランタイム」→「ランタイムの変更」→「ハードウェア アクセラレータ」\n",
"\n",
"でGPUを選択してください。"
],
"metadata": {
"id": "oUKi1NYMmXrr"
}
},
{
"cell_type": "code",
"source": [
"# (1) GPUの確認\n",
"!nvidia-smi"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "vV1t7PBRm-o6",
"outputId": "4f159c80-8114-4732-f5af-0856f7b5c39a"
},
"execution_count": 1,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Sat Dec 10 23:45:44 2022 \n",
"+-----------------------------------------------------------------------------+\n",
"| NVIDIA-SMI 460.32.03 Driver Version: 460.32.03 CUDA Version: 11.2 |\n",
"|-------------------------------+----------------------+----------------------+\n",
"| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n",
"| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n",
"| | | MIG M. |\n",
"|===============================+======================+======================|\n",
"| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 |\n",
"| N/A 70C P0 31W / 70W | 0MiB / 15109MiB | 0% Default |\n",
"| | | N/A |\n",
"+-------------------------------+----------------------+----------------------+\n",
" \n",
"+-----------------------------------------------------------------------------+\n",
"| Processes: |\n",
"| GPU GI CI PID Type Process name GPU Memory |\n",
"| ID ID Usage |\n",
"|=============================================================================|\n",
"| No running processes found |\n",
"+-----------------------------------------------------------------------------+\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# リポジトリのクローン\n",
"リポジトリをクローンします"
],
"metadata": {
"id": "sLBfykjBnjWc"
}
},
{
"cell_type": "code",
"source": [
"# (2) リポジトリのクローン\n",
"!git clone --depth 1 https://github.com/w-okada/voice-changer.git -b dev\n",
"%cd voice-changer/demo/\n",
"\n",
"!cp ../template/setting_mmvc_colab.json ../frontend/dist/assets/setting.json"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "86wTFmqsNMnD",
"outputId": "3f84319c-0365-442a-fa3a-0a8aea701926"
},
"execution_count": 2,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Cloning into 'voice-changer'...\n",
"remote: Enumerating objects: 1029, done.\u001b[K\n",
"remote: Counting objects: 100% (1029/1029), done.\u001b[K\n",
"remote: Compressing objects: 100% (919/919), done.\u001b[K\n",
"remote: Total 1029 (delta 21), reused 979 (delta 10), pack-reused 0\u001b[K\n",
"Receiving objects: 100% (1029/1029), 71.87 MiB | 26.18 MiB/s, done.\n",
"Resolving deltas: 100% (21/21), done.\n",
"/content/voice-changer/demo\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# モジュールのインストール\n",
"\n",
"必要なモジュールをインストールします。"
],
"metadata": {
"id": "8Na2PbLZSWgZ"
}
},
{
"cell_type": "code",
"source": [
"# (3) 設定ファイルの確認\n",
"!apt-get install -y espeak libsndfile1-dev &> /dev/null\n",
"!pip install unidecode &> /dev/null\n",
"!pip install phonemizer &> /dev/null\n",
"!pip install retry &> /dev/null\n",
"!pip install python-socketio &> /dev/null\n",
"!pip install fastapi &> /dev/null\n",
"!pip install python-multipart &> /dev/null\n",
"!pip install uvicorn &> /dev/null\n",
"!pip install websockets &> /dev/null\n",
"!pip install pyOpenSSL &> /dev/null\n",
"!pip install pyopenjtalk==0.2.0 &> /dev/null\n",
"\n",
"%cd MMVC_Trainer/monotonic_align/\n",
"!python setup.py build_ext --inplace\n",
"%cd ../../"
],
"metadata": {
"id": "LwZAAuqxX7yY",
"outputId": "61526e29-97d9-4975-e921-4ca9ae5a153f",
"colab": {
"base_uri": "https://localhost:8080/"
}
},
"execution_count": 3,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"/content/voice-changer/demo/MMVC_Trainer/monotonic_align\n",
"running build_ext\n",
"building 'monotonic_align.core' extension\n",
"creating build\n",
"creating build/temp.linux-x86_64-3.8\n",
"x86_64-linux-gnu-gcc -pthread -Wno-unused-result -Wsign-compare -DNDEBUG -g -fwrapv -O2 -Wall -g -fstack-protector-strong -Wformat -Werror=format-security -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 -fPIC -I/usr/local/lib/python3.8/dist-packages/numpy/core/include -I/usr/include/python3.8 -c core.c -o build/temp.linux-x86_64-3.8/core.o\n",
"x86_64-linux-gnu-gcc -pthread -shared -Wl,-O1 -Wl,-Bsymbolic-functions -Wl,-Bsymbolic-functions -g -fwrapv -O2 -Wl,-Bsymbolic-functions -g -fwrapv -O2 -g -fstack-protector-strong -Wformat -Werror=format-security -Wdate-time -D_FORTIFY_SOURCE=2 build/temp.linux-x86_64-3.8/core.o -o /content/voice-changer/demo/MMVC_Trainer/monotonic_align/monotonic_align/core.cpython-38-x86_64-linux-gnu.so\n",
"/content/voice-changer/demo\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# サーバの起動\n",
"\n",
"サーバを起動します。(4-1)\n",
"\n",
"サーバの起動状況を確認します。(4-2) \n",
"\n",
"このセルは繰り返し実行することになるのでCtrl+Retでセルを実行してください。\n",
"\n",
"下記のようなテキストが表示されたら起動完了です。\n",
"\n",
"**`DEBUG:asyncio:Using selector: EpollSelector`**\n",
"\n",
"```\n",
" Phase name:__main__\n",
" PHASE3:__main__\n",
" PHASE1:__main__\n",
"Start MMVC SocketIO Server\n",
" CONFIG:None, MODEL:None\n",
"DEBUG:asyncio:Using selector: EpollSelector\n",
"```\n",
"\n"
],
"metadata": {
"id": "-_2OcN9Borke"
}
},
{
"cell_type": "code",
"source": [
"# (4-1) サーバの起動\n",
"import random\n",
"PORT = 10000 + random.randint(1, 9999)\n",
"LOG_FILE = f\"LOG_FILE_{PORT}\"\n",
"\n",
"get_ipython().system_raw(f'python3 MMVCServerSIO.py -p {PORT} --colab True >{LOG_FILE} 2>&1 &')\n",
"#print(f\"PORT:{PORT}, LOG_FILE:{LOG_FILE}\")"
],
"metadata": {
"id": "G-nMdPxEW1rc"
},
"execution_count": 4,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# (4-2) サーバの起動確認\n",
"!sleep 5\n",
"!tail -20 {LOG_FILE}"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "chu06KpAjEK6",
"outputId": "51b4979c-91b7-4c95-eb4b-40ea001042a9"
},
"execution_count": 6,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"\u001b[32m Phase name:__main__\u001b[0m\n",
"\u001b[32m PHASE3:__main__\u001b[0m\n",
"\u001b[32m PHASE1:__main__\u001b[0m\n",
"\u001b[17mStart MMVC SocketIO Server\u001b[0m\n",
"\u001b[34m CONFIG:None, MODEL:None\u001b[0m\n",
"DEBUG:asyncio:Using selector: EpollSelector\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# プロキシを起動\n",
"ウェブサーバへのアクセスをするためのプロキシを起動します。\n",
"\n",
"表示されたURLをクリックして開くと別タブでアプリが開きます。\n",
"\n",
"Colabなので、ロードにある程度時間がかかります(30秒くらい)。"
],
"metadata": {
"id": "WhxcFLQEpctq"
}
},
{
"cell_type": "code",
"source": [
"# (5) プロキシを起動\n",
"from google.colab.output import eval_js\n",
"proxy = eval_js( \"google.colab.kernel.proxyPort(\" + str(PORT) + \")\" )\n",
"print(f\"{proxy}trainer/\")"
],
"metadata": {
"id": "nkRjZm95l87C",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"outputId": "bcca9fae-642c-42f6-f3a5-a8bb0789090f"
},
"execution_count": 7,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"https://vljlzcgk39q-496ff2e9c6d22116-17357-colab.googleusercontent.com/trainer/\n"
]
}
]
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "s1aWiBg-cTle"
},
"execution_count": null,
"outputs": []
}
]
}

221
README.md
View File

@ -1,148 +1,110 @@
## VC Client
[日本語](/README.md) /
[英語](/docs_i18n/README_en.md) /
[韓国語](/docs_i18n/README_ko.md)/
[中国語](/docs_i18n/README_zh.md)/
[ドイツ語](/docs_i18n/README_de.md)/
[アラビア語](/docs_i18n/README_ar.md)/
[ギリシャ語](/docs_i18n/README_el.md)/
[スペイン語](/docs_i18n/README_es.md)/
[フランス語](/docs_i18n/README_fr.md)/
[イタリア語](/docs_i18n/README_it.md)/
[ラテン語](/docs_i18n/README_la.md)/
[マレー語](/docs_i18n/README_ms.md)/
[ロシア語](/docs_i18n/README_ru.md)
*日本語以外は機械翻訳です。
[English](/README_en.md)
## VCClient
VCClientは、AIを用いてリアルタイム音声変換を行うソフトウェアです。
## What's New!
* v.2.0.78-beta
* bugfix: RVCモデルのアップロードエラーを回避
* ver.1.x との同時起動ができるようになりました。
* 選択できるchunk sizeを増やしました。
- v.1.5.3.9
* v.2.0.77-beta (only for RTX 5090, experimental)
* 関連モジュールを5090対応 (開発者がRTX5090未所持のため、動作未検証)
* v.2.0.76-beta
* new feature:
* Beatrice: 話者マージの実装
* Beatrice: オートピッチシフト
* bugfix:
* サーバモードのデバイス選択時の不具合対応
* v.2.0.73-beta
* new feature:
* 編集したbeatrice modelのダウンロード
* bugfix:
* beatrice v2 のpitch, formantが反映されないバグを修正
* Applio のembedderを使用しているモデルのONNXができないバグを修正
- New feature:
- Add Crepe Full/Tiny (onnx)
- some improvements:
- server info includes python version
- contentvec onnx support
- etc
- some bugfixs:
- server device mode chuttering
- new model add sample rate
- etc
## ダウンロードと関連リンク
- v.1.5.3.8a
Windows版、 M1 Mac版はhugging faceのリポジトリからダウンロードできます。
- Bugfix(test): force client device samplerate
- Bugfix: server device filter
* [VCClient のリポジトリ](https://huggingface.co/wok000/vcclient000/tree/main)
* [Light VCClient for Beatrice v2 のリポジトリ](https://huggingface.co/wok000/light_vcclient_beatrice/tree/main)
- v.1.5.3.8
*1 Linuxはリポジトリをcloneしてお使いください。
- RVC: performance improvement ([PR](https://github.com/w-okada/voice-changer/pull/371) from [nadare881](https://github.com/nadare881))
### 関連リンク
- v.1.5.3.7
* [Beatrice V2 トレーニングコードのリポジトリ](https://huggingface.co/fierce-cats/beatrice-trainer)
* [Beatrice V2 トレーニングコード Colab版](https://github.com/w-okada/beatrice-trainer-colab)
- Feature:
- server device monitor
- Bugfix:
- device output recorder button is showed in server device mode.
### 関連ソフトウェア
# VC Client とは
* [リアルタイムボイスチェンジャ VCClient](https://github.com/w-okada/voice-changer)
* [読み上げソフトウェア TTSClient](https://github.com/w-okada/ttsclient)
* [リアルタイム音声認識ソフトウェア ASRClient](https://github.com/w-okada/asrclient)
1. 各種音声変換 AI(VC, Voice Conversion)を用いてリアルタイム音声変換を行うためのクライアントソフトウェアです。サポートしている音声変換 AI は次のものになります。
## VC Clientの特徴
- サポートする音声変換 AI (サポート VC
- [MMVC](https://github.com/isletennos/MMVC_Trainer)
- [so-vits-svc](https://github.com/svc-develop-team/so-vits-svc)
- [RVC(Retrieval-based-Voice-Conversion)](https://github.com/liujing04/Retrieval-based-Voice-Conversion-WebUI)
- [DDSP-SVC](https://github.com/yxlllc/DDSP-SVC)
## 多様なAIモデルをサポート
2. 本ソフトウェアは、ネットワークを介した利用も可能であり、ゲームなどの高負荷なアプリケーションと同時に使用する場合などに音声変換処理の負荷を外部にオフロードすることができます。
| AIモデル | v.2 | v.1 | ライセンス |
| ------------------------------------------------------------------------------------------------------------ | --------- | -------------------- | ------------------------------------------------------------------------------------------ |
| [RVC ](https://github.com/RVC-Project/Retrieval-based-Voice-Conversion-WebUI/blob/main/docs/jp/README.ja.md) | supported | supported | リポジトリを参照してください。 |
| [Beatrice v1](https://prj-beatrice.com/) | n/a | supported (only win) | [独自](https://github.com/w-okada/voice-changer/tree/master/server/voice_changer/Beatrice) |
| [Beatrice v2](https://prj-beatrice.com/) | supported | n/a | [独自](https://huggingface.co/wok000/vcclient_model/blob/main/beatrice_v2_beta/readme.md) |
| [MMVC](https://github.com/isletennos/MMVC_Trainer) | n/a | supported | リポジトリを参照してください。 |
| [so-vits-svc](https://github.com/svc-develop-team/so-vits-svc) | n/a | supported | リポジトリを参照してください。 |
| [DDSP-SVC](https://github.com/yxlllc/DDSP-SVC) | n/a | supported | リポジトリを参照してください。 |
## スタンドアロン、ネットワーク経由の両構成をサポート
ローカルPCで完結した音声変換も、ネットワークを介した音声変換もサポートしています。
ネットワークを介した利用を行うことで、ゲームなどの高負荷なアプリケーションと同時に使用する場合に音声変換の負荷を外部にオフロードすることができます。
![image](https://user-images.githubusercontent.com/48346627/206640768-53f6052d-0a96-403b-a06c-6714a0b7471d.png)
3. 複数のプラットフォームに対応しています。
## 複数プラットフォームに対応
- Windows, Mac(M1), Linux, Google Colab (MMVC のみ)
Windows, Mac(M1), Linux, Google Colab
# 使用方法
*1 Linuxはリポジトリをcloneしてお使いください。
大きく 2 つの方法でご利用できます。難易度順に次の通りです。
## REST APIを提供
- 事前ビルド済みの Binary での利用
- Docker や Anaconda など環境構築を行った上での利用
各種プログラミング言語でクライアントを作成することができます。
本ソフトウェアや MMVC になじみの薄い方は上から徐々に慣れていくとよいと思います。
また、curlなどのOSに組み込まれているHTTPクライアントを使って操作ができます。
## (1) 事前ビルド済みの Binary での利用
## トラブルシュート
- 実行形式のバイナリをダウンロードして実行することができます。
[通信編](tutorials/trouble_shoot_communication_ja.md)
- チュートリアルは[こちら](tutorials/tutorial_rvc_ja_latest.md)をご覧ください。
- Windows 版と Mac 版を提供しています。
- Windows かつ Nvidia の GPU をご使用の方は、ONNX(cpu,cuda), PyTorch(cpu,cuda)をダウンロードしてください。
- Windows かつ AMD/Intel の GPU をご使用の方は、ONNX(cpu,DirectML), PyTorch(cpu,cuda)をダウンロードしてください。AMD/Intel の GPU は onnx のモデルを使用する場合のみ有効になります。
- いずれの GPU のサポート状況についても、PyTorch、Onnxruntime がサポートしている場合のみ有効になります。
- Windows で GPU をご使用にならない方は、ONNX(cpu,cuda), PyTorch(cpu,cuda)をダウンロードしてください。
- Windows 版は、ダウンロードした zip ファイルを解凍して、`start_http.bat`を実行してください。
- Mac 版はダウンロードファイルを解凍したのちに、`startHttp.command`を実行してください。開発元を検証できない旨が示される場合は、再度コントロールキーを押してクリックして実行してください(or 右クリックから実行してください)。
- 初回起動時は各種データをダウンロードします。ダウンロードに時間がかかる可能性があります。ダウンロードが完了すると、ブラウザが立ち上がります。
- リモートから接続する場合は、`.bat`ファイル(win)、`.command`ファイル(mac)の http が https に置き換わっているものを使用してください。
- DDPS-SVC の encoder は hubert-soft のみ対応です。
- ダウンロードはこちらから。
| Version | OS | フレームワーク | link | サポート VC | サイズ |
| ---------- | --- | ------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------ |
| v.1.5.3.9 | mac | ONNX(cpu), PyTorch(cpu,mps) | [google](https://drive.google.com/uc?id=1pTTcTseSdIfCyNUjB-K1mYPg9YocSYz6&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 795MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [google](https://drive.google.com/uc?id=1KWg-QoF6XmLbkUav-fmxc7bdAcD3844V&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3238MB |
| | win | ONNX(cpu,DirectML), PyTorch(cpu,cuda) | [google](https://drive.google.com/uc?id=1_TXUkDcofYz9mJd2L1ajAoyIBCQF29WL&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3123MB |
| v.1.5.3.8a | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1hg6lynE3wWJTNTParTa2qB2L06OL9KJ9&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 794MB |
| | win | ONNX(cpu,DirectML), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1C9PCu8pdafO6jJ2yCaB7x54Ls7LcM0Xc&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3122MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1bzrGhHPc9GdaRAMxkksTGtbuRLEeBx9i&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3237MB |
| v.1.5.3.8 | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1ptmjFCRDW7M0l80072JVRII5tJpF13__&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 794MB |
| | win | ONNX(cpu,DirectML), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=19DfeACmpnzqCVH5bIoFunS2pGPABRuso&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3122MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1AYP_hMdoeacX0KiF31Vd3oEjxwdreSbM&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3237MB |
| v.1.5.3.7 | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1HdJwgo0__vR6pAkOkekejUZJ0lu2NfDs&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 794MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1JIF4PvKg-8HNUv_fMaXSM3AeYa-F_c4z&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3237MB |
| | win | ONNX(cpu,DirectML), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1cJzRHmD3vk6av0Dvwj3v9Ef5KUsQYhKv&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3122MB |
(\*1) Google Drive からダウンロードできない方は[hugging_face](https://huggingface.co/wok000/vcclient000/tree/main)からダウンロードしてみてください
(\*2) 開発者が AMD のグラフィックボードを持っていないので動作確認していません。onnxruntime-directml を同梱しただけのものです。
(\*3) 解凍や起動が遅い場合、ウィルス対策ソフトのチェックが走っている可能性があります。ファイルやフォルダを対象外にして実行してみてください。(自己責任です)
## (2) Docker や Anaconda など環境構築を行った上での利用
本リポジトリをクローンして利用します。Windows では WSL2 の環境構築が必須になります。また、WSL2 上で Docker もしくは Anaconda などの仮想環境の構築が必要となります。Mac では Anaconda などの Python の仮想環境の構築が必要となります。事前準備が必要となりますが、多くの環境においてこの方法が一番高速で動きます。**<font color="red"> GPU が無くてもそこそこ新しい CPU であれば十分動く可能性があります </font>(下記のリアルタイム性の節を参照)**。
[WSL2 と Docker のインストールの解説動画](https://youtu.be/POo_Cg0eFMU)
[WSL2 と Anaconda のインストールの解説動画](https://youtu.be/fba9Zhsukqw)
Docker での実行は、[Docker を使用する](docker_vcclient/README.md)を参考にサーバを起動してください。
Anaconda の仮想環境上での実行は、[サーバ開発者向けのページ](README_dev_ja.md)を参考にサーバを起動してください。
# トラブルシュート
- [通信編](tutorials/trouble_shoot_communication_ja.md)
# リアルタイム性MMVC
GPU を使用するとほとんどタイムラグなく変換可能です。
https://twitter.com/DannadoriYellow/status/1613483372579545088?s=20&t=7CLD79h1F3dfKiTb7M8RUQ
CPU でも最近のであればそれなりの速度で変換可能。
https://twitter.com/DannadoriYellow/status/1613553862773997569?s=20&t=7CLD79h1F3dfKiTb7M8RUQ
古い CPU( i7-4770)だと、1000msec くらいかかってしまう。
# 開発者の署名について
## 開発者の署名について
本ソフトウェアは開発元の署名しておりません。下記のように警告が出ますが、コントロールキーを押しながらアイコンをクリックすると実行できるようになります。これは Apple のセキュリティポリシーによるものです。実行は自己責任となります。
![image](https://user-images.githubusercontent.com/48346627/212567711-c4a8d599-e24c-4fa3-8145-a5df7211f023.png)
# Acknowledgments
## Acknowledgments
- [立ちずんだもん素材](https://seiga.nicovideo.jp/seiga/im10792934)
- [いらすとや](https://www.irasutoya.com/)
- [つくよみちゃん](https://tyc.rei-yumesaki.net/)
* [立ちずんだもん素材](https://seiga.nicovideo.jp/seiga/im10792934)
* [いらすとや](https://www.irasutoya.com/)
* [つくよみちゃん](https://tyc.rei-yumesaki.net/)
```
本ソフトウェアの音声合成には、フリー素材キャラクター「つくよみちゃん」が無料公開している音声データを使用しています。
@ -151,12 +113,12 @@ https://twitter.com/DannadoriYellow/status/1613553862773997569?s=20&t=7CLD79h1F3
© Rei Yumesaki
```
- [あみたろの声素材工房](https://amitaro.net/)
- [れぷりかどーる](https://kikyohiroto1227.wixsite.com/kikoto-utau)
* [あみたろの声素材工房](https://amitaro.net/)
* [れぷりかどーる](https://kikyohiroto1227.wixsite.com/kikoto-utau)
# 利用規約
## 利用規約
- リアルタイムボイスチェンジャーつくよみちゃんについては、つくよみちゃんコーパスの利用規約に準じ、次の目的で変換後の音声を使用することを禁止します。
* リアルタイムボイスチェンジャーつくよみちゃんについては、つくよみちゃんコーパスの利用規約に準じ、次の目的で変換後の音声を使用することを禁止します。
```
@ -170,7 +132,7 @@ https://twitter.com/DannadoriYellow/status/1613553862773997569?s=20&t=7CLD79h1F3
※鑑賞用の作品として配布・販売していただくことは問題ございません。
```
- リアルタイムボイスチェンジャーあみたろについては、あみたろの声素材工房様の次の利用規約に準じます。詳細は[こちら](https://amitaro.net/voice/faq/#index_id6)です。
* リアルタイムボイスチェンジャーあみたろについては、あみたろの声素材工房様の次の利用規約に準じます。詳細は[こちら](https://amitaro.net/voice/faq/#index_id6)
```
あみたろの声素材やコーパス読み上げ音声を使って音声モデルを作ったり、ボイスチェンジャーや声質変換などを使用して、自分の声をあみたろの声に変換して使うのもOKです。
@ -179,27 +141,8 @@ https://twitter.com/DannadoriYellow/status/1613553862773997569?s=20&t=7CLD79h1F3
また、あみたろの声で話す内容は声素材の利用規約の範囲内のみとし、センシティブな発言などはしないでください。
```
- リアルタイムボイスチェンジャー黄琴まひろについては、れぷりかどーるの利用規約に準じます。詳細は[こちら](https://kikyohiroto1227.wixsite.com/kikoto-utau/ter%EF%BD%8Ds-of-service)です。
* リアルタイムボイスチェンジャー黄琴まひろについては、れぷりかどーるの利用規約に準じます。詳細は[こちら](https://kikyohiroto1227.wixsite.com/kikoto-utau/ter%EF%BD%8Ds-of-service)
# 免責事項
## 免責事項
本ソフトウェアの使用または使用不能により生じたいかなる直接損害・間接損害・波及的損害・結果的損害 または特別損害についても、一切責任を負いません。
# (1) レコーダー(トレーニング用音声録音アプリ)
MMVC トレーニング用の音声を簡単に録音できるアプリです。
Github Pages 上で実行できるため、ブラウザのみあれば様々なプラットフォームからご利用可能です。
録音したデータは、ブラウザ上に保存されます。外部に漏れることはありません。
[録音アプリ on Github Pages](https://w-okada.github.io/voice-changer/)
[解説動画](https://youtu.be/s_GirFEGvaA)
# 過去バージョン
| Version | OS | フレームワーク | link | サポート VC | サイズ |
| ---------- | --- | --------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | ------ |
| v.1.5.2.9e | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1W0d7I7619PcO7kjb1SPXp6MmH5Unvd78&export=download) \*1 | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 796MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1tmTMJRRggS2Sb4goU-eHlRvUBR88RZDl&export=download) \*1 | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, so-vits-svc 4.0v2, RVC, DDSP-SVC | 2872MB |
| v.1.5.3.1 | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1oswF72q_cQQeXhIn6W275qLnoBAmcrR_&export=download) \*1 | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 796MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1AWjDhW4w2Uljp1-9P8YUJBZsIlnhkJX2&export=download) \*1 | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, so-vits-svc 4.0v2, RVC, DDSP-SVC | 2872MB |

View File

@ -1,10 +1,10 @@
## For Developper
[Japanese](/README_dev_ja.md)
[Japanese](/README_dev_ja.md) [Russian](/README_dev_ru.md)
## Prerequisit
- Linux or WSL2 (not tested for Mac )
- Linux(ubuntu, debian) or WSL2, (not tested for other linux distributions and Mac)
- Anaconda
## Preparation
@ -23,6 +23,7 @@ $ git clone https://github.com/w-okada/voice-changer.git
```
## For Server Developer
1. Install requirements
```
@ -36,18 +37,28 @@ Run server with the below command. You can replace the path to each weight.
```
$ python3 MMVCServerSIO.py -p 18888 --https true \
--content_vec_500 pretrain/checkpoint_best_legacy_500.pt \
--hubert_base pretrain/hubert_base.pt \
--hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \
--nsf_hifigan pretrain/nsf_hifigan/model \
--hubert_base_jp pretrain/rinna_hubert_base_jp.pt \
--model_dir model_dir
--content_vec_500 pretrain/checkpoint_best_legacy_500.pt \
--content_vec_500_onnx pretrain/content_vec_500.onnx \
--content_vec_500_onnx_on true \
--hubert_base pretrain/hubert_base.pt \
--hubert_base_jp pretrain/rinna_hubert_base_jp.pt \
--hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \
--nsf_hifigan pretrain/nsf_hifigan/model \
--crepe_onnx_full pretrain/crepe_onnx_full.onnx \
--crepe_onnx_tiny pretrain/crepe_onnx_tiny.onnx \
--rmvpe pretrain/rmvpe.pt \
--model_dir model_dir \
--samples samples.json
```
Access with Browser (currently only chrome is supported), then you can see gui.
2-1. Trouble shoot
(1) OSError: PortAudio library not found
If you get the message below, you shold install additional library.
```
OSError: PortAudio library not found
```
@ -59,26 +70,41 @@ $ sudo apt-get install libportaudio2
$ sudo apt-get install libasound-dev
```
(2) It's not starting up! Damn software!
The client will not start automatically. Please launch your browser and access the URL displayed on the console. And watch your words.
(3) Could not load library libcudnn_cnn_infer.so.8
When using WSL, you might encounter a message saying `Could not load library libcudnn_cnn_infer.so.8. Error: libcuda.so: cannot open shared object file: No such file or directory`. This often happens because the path hasn't been properly set. Please set the path as shown below. It might be handy to add this to your launch script, such as .bashrc.
```
export LD_LIBRARY_PATH=/usr/lib/wsl/lib:$LD_LIBRARY_PATH
```
- reference
- https://qiita.com/cacaoMath/items/811146342946cdde5b83
- https://github.com/microsoft/WSL/issues/8587
3. Enjoy developing.
### Appendix
### Appendix
1. Win + Anaconda (not supported)
use conda to install pytorch
```
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
```
Also run these command.
```
pip install chardet
pip install chardet
pip install numpy==1.24.0
```
## For Client Developer
1. Import modules and initial build

View File

@ -4,7 +4,7 @@
## 前提
- Linux or WSL2 (not tested for Mac )
- Linux(ubuntu, debian) or WSL2, (not tested for other linux distributions and Mac)
- Anaconda
## 準備
@ -24,7 +24,6 @@ $ git clone https://github.com/w-okada/voice-changer.git
## サーバ開発者向け
1. モジュールをインストールする
```
@ -32,48 +31,77 @@ $ cd voice-changer/server
$ pip install -r requirements.txt
```
2. サーバを起動する
次のコマンドで起動します。各種重みについてのパスは環境に合わせて変えてください。
```
$ python3 MMVCServerSIO.py -p 18888 --https true \
--content_vec_500 pretrain/checkpoint_best_legacy_500.pt \
--hubert_base pretrain/hubert_base.pt \
--hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \
--nsf_hifigan pretrain/nsf_hifigan/model \
--hubert_base_jp pretrain/rinna_hubert_base_jp.pt \
--model_dir model_dir
--content_vec_500 pretrain/checkpoint_best_legacy_500.pt \
--content_vec_500_onnx pretrain/content_vec_500.onnx \
--content_vec_500_onnx_on true \
--hubert_base pretrain/hubert_base.pt \
--hubert_base_jp pretrain/rinna_hubert_base_jp.pt \
--hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \
--nsf_hifigan pretrain/nsf_hifigan/model \
--crepe_onnx_full pretrain/crepe_onnx_full.onnx \
--crepe_onnx_tiny pretrain/crepe_onnx_tiny.onnx \
--rmvpe pretrain/rmvpe.pt \
--model_dir model_dir \
--samples samples.json
```
ブラウザ(Chrome のみサポート)でアクセスすると画面が表示されます。
2-1. トラブルシュート
(1) OSError: PortAudio library not found
次のようなメッセージが表示される場合、追加でライブラリを追加する必要があります。
```
OSError: PortAudio library not found
```
ubuntu(wsl2)の場合下記のコマンドでインストールできます。
```
$ sudo apt-get install libportaudio2
$ sudo apt-get install libasound-dev
```
(2) 起動しないんだけど!?
自動でクライアントは起動しません。ブラウザを立ち上げてコンソールに表示された URL にアクセスしてください。
(3) Could not load library libcudnn_cnn_infer.so.8
WSL を使っていると`Could not load library libcudnn_cnn_infer.so.8. Error: libcuda.so: cannot open shared object file: No such file or directory`と表示される場合があります。
パスが通っていないことが原因のことが多いです。下記のようにパスを通して実行してください。
.bashrc など起動スクリプトに追加しておくと便利だと思います。
```
export LD_LIBRARY_PATH=/usr/lib/wsl/lib:$LD_LIBRARY_PATH
```
- 参考
- https://qiita.com/cacaoMath/items/811146342946cdde5b83
- https://github.com/microsoft/WSL/issues/8587
3. 開発しましょう
### Appendix
1. Win + Anacondaのとき (not supported)
### Appendix
1. Win + Anaconda のとき (not supported)
pytorch を conda で入れないと gpu を認識しないかもしれない。
pytorchをcondaで入れないとgpuを認識しないかもしれない。
```
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
```
また、追加で下記も必要のようだ。
```
pip install chardet
pip install chardet
pip install numpy==1.24.0
```

122
README_dev_ko.md Normal file
View File

@ -0,0 +1,122 @@
## 개발자용
[English](/README_dev_en.md) [Korean](/README_dev_ko.md)
## 전제
- Linux(ubuntu, debian) or WSL2, (다른 리눅스 배포판과 Mac에서는 테스트하지 않았습니다)
- Anaconda
## 준비
1. Anaconda 가상 환경을 작성한다
```
$ conda create -n vcclient-dev python=3.10
$ conda activate vcclient-dev
```
2. 리포지토리를 클론한다
```
$ git clone https://github.com/w-okada/voice-changer.git
```
## 서버 개발자용
1. 모듈을 설치한다
```
$ cd voice-changer/server
$ pip install -r requirements.txt
```
2. 서버를 구동한다
다음 명령어로 구동합니다. 여러 가중치에 대한 경로는 환경에 맞게 변경하세요.
```
$ python3 MMVCServerSIO.py -p 18888 --https true \
--content_vec_500 pretrain/checkpoint_best_legacy_500.pt \
--content_vec_500_onnx pretrain/content_vec_500.onnx \
--content_vec_500_onnx_on true \
--hubert_base pretrain/hubert_base.pt \
--hubert_base_jp pretrain/rinna_hubert_base_jp.pt \
--hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \
--nsf_hifigan pretrain/nsf_hifigan/model \
--crepe_onnx_full pretrain/crepe_onnx_full.onnx \
--crepe_onnx_tiny pretrain/crepe_onnx_tiny.onnx \
--rmvpe pretrain/rmvpe.pt \
--model_dir model_dir \
--samples samples.json
```
브라우저(Chrome에서만 지원)에서 접속하면 화면이 나옵니다.
2-1. 문제 해결법
(1) OSError: PortAudio library not found
다음과 같은 메시지가 나올 경우에는 추가 라이브러리를 설치해야 합니다.
```
OSError: PortAudio library not found
```
ubuntu(wsl2)인 경우에는 아래 명령어로 설치할 수 있습니다.
```
$ sudo apt-get install libportaudio2
$ sudo apt-get install libasound-dev
```
(2) 서버 구동이 안 되는데요?!
클라이언트는 자동으로 구동되지 않습니다. 브라우저를 실행하고 콘솔에 표시된 URL로 접속하세요.
(3) Could not load library libcudnn_cnn_infer.so.8
WSL를 사용 중이라면 `Could not load library libcudnn_cnn_infer.so.8. Error: libcuda.so: cannot open shared object file: No such file or directory`라는 메시지가 나오는 경우가 있습니다.
잘못된 경로가 원인인 경우가 많습니다. 아래와 같이 경로를 바꾸고 실행해 보세요.
.bashrc 등 구동 스크립트에 추가해 두면 편리합니다.
```
export LD_LIBRARY_PATH=/usr/lib/wsl/lib:$LD_LIBRARY_PATH
```
- 참고
- https://qiita.com/cacaoMath/items/811146342946cdde5b83
- https://github.com/microsoft/WSL/issues/8587
3. 개발하세요
### Appendix
1. Win + Anaconda일 때 (not supported)
pytorch를 conda가 없으면 gpu를 인식하지 않을 수 있습니다.
```
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
```
또한 추가로 아래 내용도 필요합니다.
```
pip install chardet
pip install numpy==1.24.0
```
## 클라이언트 개발자용
1. 모듈을 설치하고 한번 빌드합니다
```
cd client
cd lib
npm install
npm run build:dev
cd ../demo
npm install
npm run build:dev
```
2. 개발하세요

124
README_dev_ru.md Normal file
View File

@ -0,0 +1,124 @@
Вот перевод файла `README_dev_en.md` на русский язык:
## Для разработчиков
[Японский](/README_dev_ja.md) [Английский](/README_dev_en.md)
## Требования
- Linux (Ubuntu, Debian) или WSL2 (другие дистрибуции Linux и Mac не тестировались)
- Anaconda
## Подготовка
1. Создайте виртуальную среду Anaconda:
```
$ conda create -n vcclient-dev python=3.10
$ conda activate vcclient-dev
```
2. Клонируйте репозиторий:
```
$ git clone https://github.com/w-okada/voice-changer.git
```
## Для серверных разработчиков
1. Установите необходимые зависимости:
```
$ cd voice-changer/server
$ pip install -r requirements.txt
```
2. Запустите сервер
Запустите сервер с помощью следующей команды. Вы можете указать свои пути к весам моделей.
```
$ python3 MMVCServerSIO.py -p 18888 --https true \
--content_vec_500 pretrain/checkpoint_best_legacy_500.pt \
--content_vec_500_onnx pretrain/content_vec_500.onnx \
--content_vec_500_onnx_on true \
--hubert_base pretrain/hubert_base.pt \
--hubert_base_jp pretrain/rinna_hubert_base_jp.pt \
--hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \
--nsf_hifigan pretrain/nsf_hifigan/model \
--crepe_onnx_full pretrain/crepe_onnx_full.onnx \
--crepe_onnx_tiny pretrain/crepe_onnx_tiny.onnx \
--rmvpe pretrain/rmvpe.pt \
--model_dir model_dir \
--samples samples.json
```
Откройте браузер (на данный момент поддерживается только Chrome), и вы увидите графический интерфейс.
2-1. Устранение неполадок
(1) OSError: не найдена библиотека PortAudio
Если вы получите сообщение ниже, необходимо установить дополнительную библиотеку:
```
OSError: PortAudio library not found
```
Вы можете установить библиотеку командой:
```
$ sudo apt-get install libportaudio2
$ sudo apt-get install libasound-dev
```
(2) Не запускается! Чертова программа!
Клиент не запускается автоматически. Пожалуйста, откройте браузер и перейдите по URL, отображаемому в консоли. И будьте осторожны со словами.
(3) Не удалось загрузить библиотеку libcudnn_cnn_infer.so.8
При использовании WSL может возникнуть ошибка `Could not load library libcudnn_cnn_infer.so.8. Error: libcuda.so: cannot open shared object file: No such file or directory`. Это часто связано с тем, что путь к библиотеке не установлен. Установите путь с помощью команды ниже. Вы можете добавить эту команду в ваш скрипт запуска, например, в .bashrc.
```
export LD_LIBRARY_PATH=/usr/lib/wsl/lib:$LD_LIBRARY_PATH
```
- ссылки:
- https://qiita.com/cacaoMath/items/811146342946cdde5b83
- https://github.com/microsoft/WSL/issues/8587
3. Наслаждайтесь разработкой.
### Приложение
1. Windows + Anaconda (не поддерживается)
Используйте conda для установки PyTorch:
```
conda install pytorch torchvision torchaudio pytorch-cuda=11.8 -c pytorch -c nvidia
```
Также выполните эти команды:
```
pip install chardet
pip install numpy==1.24.0
```
## Для клиентских разработчиков
1. Импорт модулей и начальная сборка:
```
cd client
cd lib
npm install
npm run build:dev
cd ../demo
npm install
npm run build:dev
```
2. Наслаждайтесь.

View File

@ -1,55 +1,53 @@
## VC Client
[Japanese](/README_ja.md)
[Japanese](/README_ja.md) [Korean](/README_ko.md) [Russian](/README_ru.md)
## What's New!
- v.1.5.3.9
- New feature:
- Add Crepe Full/Tiny (onnx)
- some improvements:
- server info includes python version
- contentvec onnx support
- etc
- some bugfixs:
- server device mode chuttering
- new model add sample rate
- etc
- v.1.5.3.8a
- Bugfix(test): force client device samplerate
- Bugfix: server device filter
- v.1.5.3.8
- RVC: performance improvement ([PR](https://github.com/w-okada/voice-changer/pull/371) from [nadare881](https://github.com/nadare881))
- v.1.5.3.7
- Feature:
- server device monitor
- Bugfix:
- device output recorder button is showed in server device mode.
- We have released a sister product, the Text To Speech client.
- You can enjoy voice generation with a simple interface.
- For more details, click [here](https://github.com/w-okada/ttsclient).
- Beatrice V2 Training Code Released!!!
- [Training Code Repository](https://huggingface.co/fierce-cats/beatrice-trainer)
- [Colab Version](https://github.com/w-okada/beatrice-trainer-colab)
- v.2.0.70-beta (only for m1 mac)
- [HERE](https://github.com/w-okada/voice-changer/tree/v.2)
- new feature:
- The M1 Mac version of VCClient now supports Beatrice v2 beta.1.
- v.2.0.69-beta (only for win)
- [HERE](https://github.com/w-okada/voice-changer/tree/v.2)
- bugfix:
- Fixed a bug where the start button would not be displayed in case of some exceptions
- Adjusted the output buffer for server device mode
- Fixed a bug where the sampling rate would change when settings were modified while using server device mode
- Fixed a bug when using Japanese hubert
- misc:
- Added host API filter (highlighted) for server device mode
- v.2.0.65-beta
- [HERE](https://github.com/w-okada/voice-changer/tree/v.2)
- new feature: We have supported Beatrice v2 beta.1, enabling even higher quality voice conversion.
# What is VC Client
1. This is a client software for performing real-time voice conversion using various Voice Conversion (VC) AI. The supported AI for voice conversion are as follows.
- [MMVC](https://github.com/isletennos/MMVC_Trainer)
- [so-vits-svc](https://github.com/svc-develop-team/so-vits-svc)
- [MMVC](https://github.com/isletennos/MMVC_Trainer) (only v1)
- [so-vits-svc](https://github.com/svc-develop-team/so-vits-svc) (only v1)
- [RVC(Retrieval-based-Voice-Conversion)](https://github.com/liujing04/Retrieval-based-Voice-Conversion-WebUI)
- [DDSP-SVC](https://github.com/yxlllc/DDSP-SVC)
- [DDSP-SVC](https://github.com/yxlllc/DDSP-SVC) (only v1)
- [Beatrice JVS Corpus Edition](https://prj-beatrice.com/) * experimental, (***NOT MIT License*** see [readme](https://github.com/w-okada/voice-changer/blob/master/server/voice_changer/Beatrice/)) * Only for Windows, CPU dependent (only v1)
- [Beatrice v2](https://prj-beatrice.com/) (only for v2)
2. Distribute the load by running Voice Changer on a different PC
1. Distribute the load by running Voice Changer on a different PC
The real-time voice changer of this application works on a server-client configuration. By running the MMVC server on a separate PC, you can run it while minimizing the impact on other resource-intensive processes such as gaming commentary.
![image](https://user-images.githubusercontent.com/48346627/206640768-53f6052d-0a96-403b-a06c-6714a0b7471d.png)
3. Cross-platform compatibility
Supports Windows, Mac (including Apple Silicon M1), Linux, and Google Colaboratory.
## Related Software
- [Real-time Voice Changer VCClient](https://github.com/w-okada/voice-changer)
- [Text-to-Speech Software TTSClient](https://github.com/w-okada/ttsclient)
- [Real-Time Speech Recognition Software ASRClient](https://github.com/w-okada/asrclient)
# usage
This is an app for performing voice changes with MMVC and so-vits-svc.
@ -63,10 +61,19 @@ It can be used in two main ways, in order of difficulty:
- You can download and run executable binaries.
- Please see [here](tutorials/tutorial_rvc_en_latest.md) for the tutorial.
- Please see [here](tutorials/tutorial_rvc_en_latest.md) for the tutorial. ([trouble shoot](https://github.com/w-okada/voice-changer/blob/master/tutorials/trouble_shoot_communication_ja.md))
- We offer Windows and Mac versions.
- It's now easy to try it out on [Google Colaboratory](https://github.com/w-okada/voice-changer/tree/v.2/w_okada's_Voice_Changer_version_2_x.ipynb) (requires a ngrok account). You can launch it from the 'Open in Colab' button in the top left corner.
<img src="https://github.com/w-okada/voice-changer/assets/48346627/3f092e2d-6834-42f6-bbfd-7d389111604e" width="400" height="150">
- We offer Windows and Mac versions on [hugging face](https://huggingface.co/wok000/vcclient000/tree/main)
- v2 for Windows
- Please download and use `vcclient_win_std_xxx.zip`. You can perform voice conversion using a reasonably high-performance CPU without a GPU, or by utilizing DirectML to leverage GPUs (AMD, Nvidia). v2 supports both torch and onnx.
- If you have an Nvidia GPU, you can achieve faster voice conversion by using `vcclient_win_cuda_xxx.zip`.
- v2 for Mac (Apple Silicon)
- Please download and use `vcclient_mac_xxx.zip`.
- v1
- If you are using a Windows and Nvidia GPU, please download ONNX (cpu, cuda), PyTorch (cpu, cuda).
- If you are using a Windows and AMD/Intel GPU, please download ONNX (cpu, DirectML) and PyTorch (cpu, cuda). AMD/Intel GPUs are only enabled for ONNX models.
- In either case, for GPU support, PyTorch and Onnxruntime are only enabled if supported.
@ -80,26 +87,7 @@ It can be used in two main ways, in order of difficulty:
- The encoder of DDPS-SVC only supports hubert-soft.
- Download (When you cannot download from google drive, try [hugging_face](https://huggingface.co/wok000/vcclient000/tree/main))
| Version | OS | Framework | link | support VC | size |
| ---------- | --- | ------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------- | ------ |
| v.1.5.3.9 | mac | ONNX(cpu), PyTorch(cpu,mps) | [google](https://drive.google.com/uc?id=1pTTcTseSdIfCyNUjB-K1mYPg9YocSYz6&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 795MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [google](https://drive.google.com/uc?id=1KWg-QoF6XmLbkUav-fmxc7bdAcD3844V&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3238MB |
| | win | ONNX(cpu,DirectML), PyTorch(cpu,cuda) | [google](https://drive.google.com/uc?id=1_TXUkDcofYz9mJd2L1ajAoyIBCQF29WL&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3123MB |
| v.1.5.3.8a | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1hg6lynE3wWJTNTParTa2qB2L06OL9KJ9&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 794MB |
| | win | ONNX(cpu,DirectML), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1C9PCu8pdafO6jJ2yCaB7x54Ls7LcM0Xc&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3122MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1bzrGhHPc9GdaRAMxkksTGtbuRLEeBx9i&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3237MB |
| v.1.5.3.8 | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1ptmjFCRDW7M0l80072JVRII5tJpF13__&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 794MB |
| | win | ONNX(cpu,DirectML), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=19DfeACmpnzqCVH5bIoFunS2pGPABRuso&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3122MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1AYP_hMdoeacX0KiF31Vd3oEjxwdreSbM&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3237MB |
| v.1.5.3.7 | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1HdJwgo0__vR6pAkOkekejUZJ0lu2NfDs&export=download), [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 794MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1JIF4PvKg-8HNUv_fMaXSM3AeYa-F_c4z&export=download) , [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3237MB |
| | win | ONNX(cpu,DirectML), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1cJzRHmD3vk6av0Dvwj3v9Ef5KUsQYhKv&export=download) , [hugging face](https://huggingface.co/wok000/vcclient000/tree/main) | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC, DDSP-SVC | 3122MB |
(\*1) You can also download from [hugging_face](https://huggingface.co/wok000/vcclient000/tree/main)
(\*2) The developer does not have an AMD graphics card, so it has not been tested. This package only includes onnxruntime-directml.
(\*3) If unpacking or starting is slow, there is a possibility that virus checking is running on your antivirus software. Please try running it with the file or folder excluded from the target. (At your own risk)
- [Download from hugging face](https://huggingface.co/wok000/vcclient000/tree/main)
## (2) Usage after setting up the environment such as Docker or Anaconda
@ -113,17 +101,8 @@ To run docker, see [start docker](docker_vcclient/README_en.md).
To run on Anaconda venv, see [server developer's guide](README_dev_en.md)
# Real-time performance
To run on Linux using an AMD GPU, see [setup guide linux](tutorials/tutorial_anaconda_amd_rocm.md)
Conversion is almost instantaneous when using GPU.
https://twitter.com/DannadoriYellow/status/1613483372579545088?s=20&t=7CLD79h1F3dfKiTb7M8RUQ
Even with CPU, recent ones can perform conversions at a reasonable speed.
https://twitter.com/DannadoriYellow/status/1613553862773997569?s=20&t=7CLD79h1F3dfKiTb7M8RUQ
With an old CPU (i7-4770), it takes about 1000 msec for conversion.
# Software Signing

185
README_ko.md Normal file
View File

@ -0,0 +1,185 @@
## VC Client
[English](/README_en.md) [Korean](/README_ko.md)
## 새로운 기능!
- 자매품으로 텍스트 음성 변환 클라이언트를 출시하였습니다.
- 간단한 인터페이스로 음성 생성을 즐길 수 있습니다.
- 자세한 내용은 [여기](https://github.com/w-okada/ttsclient)를 참조하세요.
- Beatrice V2 훈련 코드 공개!!!
- [훈련 코드 리포지토리](https://huggingface.co/fierce-cats/beatrice-trainer)
- [Colab 버전](https://github.com/w-okada/beatrice-trainer-colab)
- v.2.0.70-beta (only for m1 mac)
- [여기를 참조하십시오](https://github.com/w-okada/voice-changer/tree/v.2)
- new feature:
- M1 Mac 버전 VCClient에서도 Beatrice v2 beta.1을 지원합니다.
- v.2.0.69-beta (only for win)
- [여기를 참조하십시오](https://github.com/w-okada/voice-changer/tree/v.2)
- 버그 수정:
- 일부 예외 발생 시 시작 버튼이 표시되지 않는 버그를 수정
- 서버 디바이스 모드의 출력 버퍼 조정
- 서버 디바이스 모드 사용 중 설정 변경 시 샘플링 레이트가 변하는 버그 수정
- 일본어 hubert 사용 시 버그 수정
- 기타:
- 서버 디바이스 모드에 호스트 API 필터 추가 (강조 표시)
- v.2.0.65-beta
- [여기를 참조하십시오](https://github.com/w-okada/voice-changer/tree/v.2)
- new feature: Beatrice v2 beta.1를 지원하여 더 높은 품질의 음성 변환이 가능해졌습니다
# VC Client란
1. 각종 음성 변환 AI(VC, Voice Conversion)를 활용해 실시간 음성 변환을 하기 위한 클라이언트 소프트웨어입니다. 지원하는 음성 변환 AI는 다음과 같습니다.
- 지원하는 음성 변환 AI (지원 VC)
- [MMVC](https://github.com/isletennos/MMVC_Trainer) (only v1)
- [so-vits-svc](https://github.com/svc-develop-team/so-vits-svc) (only v1)
- [RVC(Retrieval-based-Voice-Conversion)](https://github.com/liujing04/Retrieval-based-Voice-Conversion-WebUI)
- [DDSP-SVC](https://github.com/yxlllc/DDSP-SVC) (only v1)
- [Beatrice JVS Corpus Edition](https://prj-beatrice.com/) * experimental, (***NOT MIT License*** see [readme](https://github.com/w-okada/voice-changer/blob/master/server/voice_changer/Beatrice/)) * Only for Windows, CPU dependent (only v1)
- [Beatrice v2](https://prj-beatrice.com/) (only for v2)
-
1. 이 소프트웨어는 네트워크를 통한 사용도 가능하며, 게임 등 부하가 큰 애플리케이션과 동시에 사용할 경우 음성 변화 처리의 부하를 외부로 돌릴 수도 있습니다.
![image](https://user-images.githubusercontent.com/48346627/206640768-53f6052d-0a96-403b-a06c-6714a0b7471d.png)
3. 여러 플랫폼을 지원합니다.
- Windows, Mac(M1), Linux, Google Colab (MMVC만 지원)
## 관련 소프트웨어
- [실시간 음성 변조기 VCClient](https://github.com/w-okada/voice-changer)
- [텍스트 읽기 소프트웨어 TTSClient](https://github.com/w-okada/ttsclient)
- [실시간 음성 인식 소프트웨어 ASRClient](https://github.com/w-okada/asrclient)
# 사용 방법
크게 두 가지 방법으로 사용할 수 있습니다. 난이도 순서는 다음과 같습니다.
- 사전 빌드된 Binary 사용
- Docker, Anaconda 등으로 구축된 개발 환경에서 사용
이 소프트웨어나 MMVC에 익숙하지 않은 분들은 위에서부터 차근차근 익숙해지길 추천합니다.
## (1) 사전 빌드된 Binary(파일) 사용
- 실행 형식 바이너리를 다운로드하여 실행할 수 있습니다.
- 튜토리얼은 [이곳](tutorials/tutorial_rvc_ko_latest.md)을 확인하세요。([네트워크 문제 해결법](https://github.com/w-okada/voice-changer/blob/master/tutorials/trouble_shoot_communication_ko.md))
- [Google Colaboratory](https://github.com/w-okada/voice-changer/tree/v.2/w_okada's_Voice_Changer_version_2_x.ipynb) で簡単にお試しいただけるようになりました。左上の Open in Colab のボタンから起動できます。
<img src="https://github.com/w-okada/voice-changer/assets/48346627/3f092e2d-6834-42f6-bbfd-7d389111604e" width="400" height="150">
- Windows 버전과 Mac 버전을 제공하고 있습니다. [Hugging Face](https://huggingface.co/wok000/vcclient000/tree/main)에서 다운로드할 수 있습니다.
- Windows용 v2
- `vcclient_win_std_xxx.zip`를 다운로드하여 사용하세요. GPU를 사용하지 않고도 (어느 정도 고성능의) CPU를 사용한 음성 변환이나, DirectML을 사용해 GPU(AMD, Nvidia)를 활용한 음성 변환이 가능합니다. v2에서는 torch와 onnx 모두를 지원합니다.
- Nvidia GPU를 가지고 계신 분들은 `vcclient_win_cuda_xxx.zip`를 사용하시면 더 빠른 음성 변환이 가능합니다.
- Mac (Apple Silicon)용 v2
- `vcclient_mac_xxx.zip`를 다운로드하여 사용하세요.
- v1
- Windows와 NVIDIA GPU를 사용하는 분은 ONNX(cpu, cuda), PyTorch(cpu, cuda)를 다운로드하세요.
- Windows와 AMD/Intel GPU를 사용하는 분은 ONNX(cpu, DirectML), PyTorch(cpu, cuda)를 다운로드하세요 AMD/Intel GPU는 ONNX 모델을 사용할 때만 적용됩니다.
- 그 외 GPU도 PyTorch, Onnxruntime가 지원할 경우에만 적용됩니다.
- Windows에서 GPU를 사용하지 않는 분은 ONNX(cpu, cuda), PyTorch(cpu, cuda)를 다운로드하세요.
- Windows 버전은 다운로드한 zip 파일의 압축을 풀고 `start_http.bat`를 실행하세요.
- Mac 버전은 다운로드한 파일을 풀고 `startHttp.command`를 실행하세요. 확인되지 않은 개발자 메시지가 나오면 다시 control 키를 누르고 클릭해 실행하세요(or 오른쪽 클릭으로 실행하세요).
- 처음 실행할 때는 인터넷으로 여러 데이터를 다운로드합니다. 다운로드할 때 시간이 좀 걸릴 수 있습니다. 다운로드가 완료되면 브라우저가 실행됩니다.
- 원격으로 접속할 때는 http 대신 https `.bat` 파일(win)、`.command` 파일(mac)을 실행하세요.
- DDPS-SVC의 encoder는 hubert-soft만 지원합니다.
## (2) Docker나 Anaconda 등으로 구축된 개발 환경에서 사용
이 리포지토리를 클론해 사용할 수 있습니다. Windows에서는 WSL2 환경 구축이 필수입니다. 또한, WSL2 상에 Docker나 Anaconda 등의 가상환경 구축이 필요합니다. Mac에서는 Anaconda 등의 Python 가상환경 구축이 필요합니다. 사전 준비가 필요하지만, 많은 환경에서 이 방법이 가장 빠르게 작동합니다. **<font color="red"> GPU가 없어도 나름 최근 출시된 CPU가 있다면 충분히 작동할 가능성이 있습니다</font>(아래 실시간성 항목 참조)**.
[WSL2와 Docker 설치 설명 영상](https://youtu.be/POo_Cg0eFMU)
[WSL2와 Anaconda 설치 설명 영상](https://youtu.be/fba9Zhsukqw)
Docker에서 실행은 [Docker를 사용](docker_vcclient/README_ko.md)을 참고해 서버를 구동하세요.
Anaconda 가상 환경에서 실행은 [서버 개발자용 문서](README_dev_ko.md)를 참고해 서버를 구동하세요.
# 문제 해결법
- [통신편](tutorials/trouble_shoot_communication_ko.md)
# 개발자 서명에 대하여
이 소프트웨어는 개발자 서명이 없습니다. 本ソフトウェアは開発元の署名しておりません。下記のように警告が出ますが、コントロールキーを押しながらアイコンをクリックすると実行できるようになります。これは Apple のセキュリティポリシーによるものです。実行は自己責任となります。
![image](https://user-images.githubusercontent.com/48346627/212567711-c4a8d599-e24c-4fa3-8145-a5df7211f023.png)
(이미지 번역: ctrl을 누른 채로 클릭)
# 감사의 말
- [立ちずんだもん素材](https://seiga.nicovideo.jp/seiga/im10792934)
- [いらすとや](https://www.irasutoya.com/)
- [つくよみちゃん](https://tyc.rei-yumesaki.net/)
```
이 소프트웨어의 음성 합성에는 무료 소재 캐릭터 「つくよみちゃん(츠쿠요미 짱)」이 무료 공개하고 있는 음성 데이터를 사용했습니다.■츠쿠요미 짱 말뭉치(CV.夢前黎)
https://tyc.rei-yumesaki.net/material/corpus/
© Rei Yumesaki
```
- [あみたろの声素材工房](https://amitaro.net/)
- [れぷりかどーる](https://kikyohiroto1227.wixsite.com/kikoto-utau)
# 이용약관
- 실시간 음성 변환기 츠쿠요미 짱은 츠쿠요미 짱 말뭉치 이용약관에 따라 다음과 같은 목적으로 변환 후 음성을 사용하는 것을 금지합니다.
```
■사람을 비판·공격하는 행위. ("비판·공격"의 정의는 츠쿠요미 짱 캐릭터 라이센스에 준합니다)
■특정 정치적 입장·종교·사상에 대한 찬반을 논하는 행위.
■자극적인 표현물을 무분별하게 공개하는 행위.
■타인에게 2차 창작(소재로서의 활용)을 허가하는 형태로 공개하는 행위.
※감상용 작품으로서 배포·판매하는 건 문제없습니다.
```
- 실시간 음성 변환기 아미타로는 あみたろの声素材工房(아미타로의 음성 소재 공방)의 다음 이용약관에 따릅니다. 자세한 내용은 [이곳](https://amitaro.net/voice/faq/#index_id6)에 있습니다.
```
아미타로의 음성 소재나 말뭉치 음성으로 음성 모델을 만들거나, 음성 변환기나 말투 변환기 등을 사용해 본인 목소리를 아미타로의 목소리로 변환해 사용하는 것도 괜찮습니다.
단, 그 경우에는 반드시 아미타로(혹은 코하루네 아미)의 음성으로 변환한 것을 명시하고, 아미타로(및 코하루네 아미)가 말하는 것이 아님을 누구나 알 수 있도록 하십시오.
또한 아미타로의 음성으로 말하는 내용은 음성 소재 이용약관의 범위 내에서만 사용해야 하며, 민감한 발언은 삼가십시오.
```
- 실시간 음성 변환기 키코토 마히로는 れぷりかどーる(레플리카 돌)의 이용약관에 따릅니다. 자세한 내용은 [이곳](https://kikyohiroto1227.wixsite.com/kikoto-utau/ter%EF%BD%8Ds-of-service)에 있습니다.
# 면책 사항
이 소프트웨어의 사용 또는 사용 불능으로 인해 발생한 직접 손해·간접 손해·파생적 손해·결과적 손해 또는 특별 손해에 대해 모든 책임을 지지 않습니다.
# (1) 레코더(트레이닝용 음성 녹음 앱)
MMVC 트레이닝용 음성을 간단하게 녹음할 수 있는 앱입니다.
Github Pages에서 실행할 수 있어서 브라우저만 있으면 다양한 플랫폼에서 사용할 수 있습니다.
녹음한 데이터는 브라우저에 저장됩니다. 외부로 유출되지 않습니다.
[녹음 앱 on Github Pages](https://w-okada.github.io/voice-changer/)
[설명 영상](https://youtu.be/s_GirFEGvaA)
# 이전 버전
| Version | OS | 프레임워크 | link | 지원 VC | 파일 크기 |
| ---------- | --- | --------------------------------- | ---------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------- | --------- |
| v.1.5.2.9e | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1W0d7I7619PcO7kjb1SPXp6MmH5Unvd78&export=download) \*1 | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 796MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1tmTMJRRggS2Sb4goU-eHlRvUBR88RZDl&export=download) \*1 | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, so-vits-svc 4.0v2, RVC, DDSP-SVC | 2872MB |
| v.1.5.3.1 | mac | ONNX(cpu), PyTorch(cpu,mps) | [normal](https://drive.google.com/uc?id=1oswF72q_cQQeXhIn6W275qLnoBAmcrR_&export=download) \*1 | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, RVC | 796MB |
| | win | ONNX(cpu,cuda), PyTorch(cpu,cuda) | [normal](https://drive.google.com/uc?id=1AWjDhW4w2Uljp1-9P8YUJBZsIlnhkJX2&export=download) \*1 | MMVC v.1.5.x, MMVC v.1.3.x, so-vits-svc 4.0, so-vits-svc 4.0v2, RVC, DDSP-SVC | 2872MB |
# For Contributor
이 리포지토리는 [CLA](https://raw.githubusercontent.com/w-okada/voice-changer/master/LICENSE-CLA)를 설정했습니다.

119
README_ru.md Normal file
View File

@ -0,0 +1,119 @@
[Японский](/README_ja.md) [Корейский](/README_ko.md) [Английский](/README_en.md)
## Что нового!
- Мы выпустили продукт-сестру - клиент Text To Speech.
- Вы можете насладиться генерацией голоса через простой интерфейс.
- Подробнее [здесь](https://github.com/w-okada/ttsclient).
- Код тренировки Beatrice V2 теперь доступен!
- [Репозиторий кода тренировки](https://huggingface.co/fierce-cats/beatrice-trainer)
- [Версия для Colab](https://github.com/w-okada/beatrice-trainer-colab)
- v.2.0.70-beta (only for m1 mac)
- [HERE](https://github.com/w-okada/voice-changer/tree/v.2)
- new feature:
- В версии VCClient для Mac на базе M1 теперь поддерживается Beatrice v2 beta.1.
- v.2.0.69-beta (only for win)
- [HERE](https://github.com/w-okada/voice-changer/tree/v.2)
- Исправления ошибок:
- Исправлена ошибка, из-за которой кнопка запуска не отображалась в случае некоторых исключений
- Настроен выходной буфер для режима серверного устройства
- Исправлена ошибка, при которой изменялась частота дискретизации при изменении настроек в режиме серверного устройства
- Исправлена ошибка при использовании японского hubert
- Прочее:
- Добавлен фильтр API хоста (выделено) для режима серверного устройства
- v.2.0.65-beta
- [HERE](https://github.com/w-okada/voice-changer/tree/v.2)
- new feature: We have supported Beatrice v2 beta.1, enabling even higher quality voice conversion.
# Что такое VC Клиент
1. Это клиентское ПО для выполнения преобразования голоса в реальном времени с использованием различных AI для преобразования голоса. Поддерживаемые AI:
- [MMVC](https://github.com/isletennos/MMVC_Trainer) (только v1)
- [so-vits-svc](https://github.com/svc-develop-team/so-vits-svc) (только v1)
- [RVC (Retrieval-based Voice Conversion)](https://github.com/liujing04/Retrieval-based-Voice-Conversion-WebUI)
- [DDSP-SVC](https://github.com/yxlllc/DDSP-SVC) (только v1)
- [Beatrice JVS Corpus Edition](https://prj-beatrice.com/) * экспериментальный * (не по лицензии MIT, см. [readme](https://github.com/w-okada/voice-changer/blob/master/server/voice_changer/Beatrice/)), только для Windows, зависит от процессора (только v1)
- [Beatrice v2](https://prj-beatrice.com/) (только v2)
2. Распределение нагрузки между разными ПК
Реализация преобразования голоса работает по схеме "сервер-клиент". Вы можете запустить сервер MMVC на отдельном ПК для минимизации влияния на другие ресурсоёмкие процессы, такие как стриминг.
![image](https://user-images.githubusercontent.com/48346627/206640768-53f6052d-0a96-403b-a06c-6714a0b7471d.png)
3. Кроссплатформенная совместимость
Поддержка Windows, Mac (включая Apple Silicon M1), Linux и Google Colaboratory.
# Как использовать
Это приложение для изменения голоса с использованием MMVC и so-vits-svc.
Есть два основных способа использования, в порядке сложности:
- Использование готового исполняемого файла
- Настройка окружения с Docker или Anaconda
## (1) Использование готовых исполняемых файлов
- Вы можете скачать и запустить исполняемые файлы.
- Смотрите [здесь](tutorials/tutorial_rvc_en_latest.md) для получения руководства. ([устранение неполадок](https://github.com/w-okada/voice-changer/blob/master/tutorials/trouble_shoot_communication_ja.md))
- Теперь попробовать можно на [Google Colaboratory](https://github.com/w-okada/voice-changer/tree/v.2/w_okada's_Voice_Changer_version_2_x.ipynb) (требуется аккаунт ngrok). Вы можете запустить его через кнопку "Открыть в Colab" в верхнем левом углу.
<img src="https://github.com/w-okada/voice-changer/assets/48346627/3f092e2d-6834-42f6-bbfd-7d389111604e" width="400" height="150">
- Мы предлагаем версии для Windows и Mac на [hugging face](https://huggingface.co/wok000/vcclient000/tree/main)
- v2 для Windows
- Пожалуйста, скачайте и используйте `vcclient_win_std_xxx.zip`. Преобразование голоса можно выполнять с использованием мощного процессора без GPU или с использованием DirectML для GPU (AMD, Nvidia). v2 поддерживает как torch, так и onnx.
- Если у вас Nvidia GPU, скачайте `vcclient_win_cuda_xxx.zip` для более быстрого преобразования.
- v2 для Mac (Apple Silicon)
- Пожалуйста, скачайте и используйте `vcclient_mac_xxx.zip`.
- v1
- Для Windows с Nvidia GPU скачайте ONNX (cpu, cuda), PyTorch (cpu, cuda).
- Для Windows с AMD/Intel GPU скачайте ONNX (cpu, DirectML) и PyTorch (cpu, cuda). AMD/Intel GPU поддерживаются только для ONNX моделей.
- Для пользователей Windows: после распаковки zip-файла запустите соответствующий `start_http.bat` файл.
- Для Mac: после распаковки zip-файла дважды щёлкните на `startHttp.command`. Если появится сообщение о невозможности проверки разработчика, нажмите Ctrl и повторно запустите.
- Если подключаетесь удалённо, используйте `.command` (Mac) или `.bat` (Windows) файл с https вместо http.
- Энкодер DDPS-SVC поддерживает только hubert-soft.
- [Скачать с hugging face](https://huggingface.co/wok000/vcclient000/tree/main)
## (2) Использование после настройки окружения с Docker или Anaconda
Клонируйте этот репозиторий и используйте его. Для Windows требуется настройка WSL2. Для Mac нужно настроить виртуальные среды Python, например Anaconda. Этот метод обеспечивает наивысшую скорость в большинстве случаев. **<font color="red"> Даже без GPU можно получить достаточную производительность на современном процессоре </font>(смотрите раздел о производительности в реальном времени ниже)**.
[Видео-инструкция по установке WSL2 и Docker](https://youtu.be/POo_Cg0eFMU)
[Видео-инструкция по установке WSL2 и Anaconda](https://youtu.be/fba9Zhsukqw)
Для запуска Docker смотрите [start docker](docker_vcclient/README_en.md).
Для запуска на Anaconda venv смотрите [руководство разработчика](README_dev_ru.md).
Для запуска на Linux с AMD GPU смотрите [руководство](tutorials/tutorial_anaconda_amd_rocm.md).
# Подпись программного обеспечения
Это ПО не подписано разработчиком. Появится предупреждение, но его можно запустить, нажав на иконку с удержанием клавиши Ctrl. Это связано с политикой безопасности Apple. Использование ПО на ваш риск.
![image](https://user-images.githubusercontent.com/48346627/212567711-c4a8d599-e24c-4fa3-8145-a5df7211f023.png)
https://user-images.githubusercontent.com/48346627/212569645-e30b7f4e-079d-4504-8cf8-7816c5f40b00.mp4
# Благодарности
- [Материалы Tachizunda-mon](https://seiga.nicovideo.jp/seiga/im10792934)
- [Irasutoya](https://www.irasutoya.com/)
- [Tsukuyomi-chan](https://tyc.rei-yumesaki.net)
> Это ПО использует голосовые данные бесплатного материала персонажа "Цукуёми-тян", предоставленного CV. Юмесаки Рэй.
>
> - Корпус Цукуёми-тян (CV. Юмесаки Рэй)
>
> https://tyc.rei-yumesaki.net/material/corpus/
>
> Авторское право. Юмесаки Рэй, Все права защищены.

View File

@ -0,0 +1,206 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/github/w-okada/voice-changer/blob/master/Realtime_Voice_Changer_on_Colab.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"### [w-okada's Voice Changer](https://github.com/w-okada/voice-changer) | **Colab**\n",
"\n",
"---\n",
"\n",
"## **⬇ VERY IMPORTANT ⬇**\n",
"\n",
"You can use the following settings for better results:\n",
"\n",
"If you're using a index: `f0: RMVPE_ONNX | Chunk: 112 or higher | Extra: 8192`<br>\n",
"If you're not using a index: `f0: RMVPE_ONNX | Chunk: 96 or higher | Extra: 16384`<br>\n",
"**Don't forget to select a T4 GPU in the GPU field, <b>NEVER</b> use CPU!\n",
"> Seems that PTH models performance better than ONNX for now, you can still try ONNX models and see if it satisfies you\n",
"\n",
"\n",
"*You can always [click here](https://github.com/YunaOneeChan/Voice-Changer-Settings) to check if these settings are up-to-date*\n",
"\n",
"---\n",
"\n",
"### <font color=red>⬇ Always use Colab GPU! (**IMPORTANT!**) ⬇</font>\n",
"You need to use a Colab GPU so the Voice Changer can work faster and better\\\n",
"Use the menu above and click on **Runtime** » **Change runtime** » **Hardware acceleration** to select a GPU (**T4 is the free one**)\n",
"\n",
"---\n",
"**Credits**<br>\n",
"Realtime Voice Changer by [w-okada](https://github.com/w-okada)<br>\n",
"Notebook files updated by [rafacasari](https://github.com/Rafacasari)<br>\n",
"Recommended settings by [YunaOneeChan](https://github.com/YunaOneeChan)\n",
"\n",
"**Need help?** [AI Hub Discord](https://discord.gg/aihub) » ***#help-realtime-vc***\n",
"\n",
"---"
],
"metadata": {
"id": "Lbbmx_Vjl0zo"
}
},
{
"cell_type": "code",
"source": [
"# @title Clone repository and install dependencies\n",
"# @markdown This first step will download the latest version of Voice Changer and install the dependencies. **It can take some time to complete.**\n",
"%cd /content/\n",
"\n",
"!pip install colorama --quiet\n",
"from colorama import Fore, Style\n",
"import os\n",
"\n",
"print(f\"{Fore.CYAN}> Cloning the repository...{Style.RESET_ALL}\")\n",
"!git clone https://github.com/w-okada/voice-changer.git --quiet\n",
"print(f\"{Fore.GREEN}> Successfully cloned the repository!{Style.RESET_ALL}\")\n",
"%cd voice-changer/server/\n",
"\n",
"print(f\"{Fore.CYAN}> Installing libportaudio2...{Style.RESET_ALL}\")\n",
"!apt-get -y install libportaudio2 -qq\n",
"\n",
"print(f\"{Fore.CYAN}> Installing pre-dependencies...{Style.RESET_ALL}\")\n",
"# Install dependencies that are missing from requirements.txt and pyngrok\n",
"!pip install faiss-gpu fairseq pyngrok --quiet\n",
"!pip install pyworld --no-build-isolation --quiet\n",
"print(f\"{Fore.CYAN}> Installing dependencies from requirements.txt...{Style.RESET_ALL}\")\n",
"!pip install -r requirements.txt --quiet\n",
"\n",
"print(f\"{Fore.GREEN}> Successfully installed all packages!{Style.RESET_ALL}\")"
],
"metadata": {
"id": "86wTFmqsNMnD",
"cellView": "form",
"_kg_hide-output": false,
"execution": {
"iopub.status.busy": "2023-09-14T04:01:17.308284Z",
"iopub.execute_input": "2023-09-14T04:01:17.308682Z",
"iopub.status.idle": "2023-09-14T04:08:08.475375Z",
"shell.execute_reply.started": "2023-09-14T04:01:17.308652Z",
"shell.execute_reply": "2023-09-14T04:08:08.473827Z"
},
"trusted": true
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# @title Start Server **using ngrok**\n",
"# @markdown This cell will start the server, the first time that you run it will download the models, so it can take a while (~1-2 minutes)\n",
"\n",
"# @markdown ---\n",
"# @markdown You'll need a ngrok account, but <font color=green>**it's free**</font> and easy to create!\n",
"# @markdown ---\n",
"# @markdown **1** - Create a <font color=green>**free**</font> account at [ngrok](https://dashboard.ngrok.com/signup) or **login with Google/Github account**\\\n",
"# @markdown **2** - If you didn't logged in with Google/Github, you will need to **verify your e-mail**!\\\n",
"# @markdown **3** - Click [this link](https://dashboard.ngrok.com/get-started/your-authtoken) to get your auth token, and place it here:\n",
"Token = '' # @param {type:\"string\"}\n",
"# @markdown **4** - *(optional)* Change to a region near to you or keep at United States if increase latency\\\n",
"# @markdown `Default Region: us - United States (Ohio)`\n",
"Region = \"us - United States (Ohio)\" # @param [\"ap - Asia/Pacific (Singapore)\", \"au - Australia (Sydney)\",\"eu - Europe (Frankfurt)\", \"in - India (Mumbai)\",\"jp - Japan (Tokyo)\",\"sa - South America (Sao Paulo)\", \"us - United States (Ohio)\"]\n",
"\n",
"#@markdown **5** - *(optional)* Other options:\n",
"ClearConsole = True # @param {type:\"boolean\"}\n",
"\n",
"# ---------------------------------\n",
"# DO NOT TOUCH ANYTHING DOWN BELOW!\n",
"# ---------------------------------\n",
"\n",
"%cd /content/voice-changer/server\n",
"\n",
"from pyngrok import conf, ngrok\n",
"MyConfig = conf.PyngrokConfig()\n",
"MyConfig.auth_token = Token\n",
"MyConfig.region = Region[0:2]\n",
"#conf.get_default().authtoken = Token\n",
"#conf.get_default().region = Region\n",
"conf.set_default(MyConfig);\n",
"\n",
"import subprocess, threading, time, socket, urllib.request\n",
"PORT = 8000\n",
"\n",
"from pyngrok import ngrok\n",
"ngrokConnection = ngrok.connect(PORT)\n",
"public_url = ngrokConnection.public_url\n",
"\n",
"from IPython.display import clear_output\n",
"\n",
"def wait_for_server():\n",
" while True:\n",
" time.sleep(0.5)\n",
" sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n",
" result = sock.connect_ex(('127.0.0.1', PORT))\n",
" if result == 0:\n",
" break\n",
" sock.close()\n",
" if ClearConsole:\n",
" clear_output()\n",
" print(\"--------- SERVER READY! ---------\")\n",
" print(\"Your server is available at:\")\n",
" print(public_url)\n",
" print(\"---------------------------------\")\n",
"\n",
"threading.Thread(target=wait_for_server, daemon=True).start()\n",
"\n",
"!python3 MMVCServerSIO.py \\\n",
" -p {PORT} \\\n",
" --https False \\\n",
" --content_vec_500 pretrain/checkpoint_best_legacy_500.pt \\\n",
" --content_vec_500_onnx pretrain/content_vec_500.onnx \\\n",
" --content_vec_500_onnx_on true \\\n",
" --hubert_base pretrain/hubert_base.pt \\\n",
" --hubert_base_jp pretrain/rinna_hubert_base_jp.pt \\\n",
" --hubert_soft pretrain/hubert/hubert-soft-0d54a1f4.pt \\\n",
" --nsf_hifigan pretrain/nsf_hifigan/model \\\n",
" --crepe_onnx_full pretrain/crepe_onnx_full.onnx \\\n",
" --crepe_onnx_tiny pretrain/crepe_onnx_tiny.onnx \\\n",
" --rmvpe pretrain/rmvpe.pt \\\n",
" --model_dir model_dir \\\n",
" --samples samples.json\n",
"\n",
"ngrok.disconnect(ngrokConnection.public_url)"
],
"metadata": {
"id": "lLWQuUd7WW9U",
"cellView": "form",
"_kg_hide-input": false,
"scrolled": true,
"trusted": true
},
"execution_count": null,
"outputs": []
}
],
"metadata": {
"colab": {
"provenance": [],
"private_outputs": true,
"include_colab_link": true,
"gpuType": "T4",
"collapsed_sections": [
"iuf9pBHYpTn-"
]
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
},
"language_info": {
"name": "python"
},
"accelerator": "GPU"
},
"nbformat": 4,
"nbformat_minor": 0
}

File diff suppressed because it is too large Load Diff

View File

@ -1,405 +0,0 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"name": "VoiceChangerDemo",
"provenance": [],
"authorship_tag": "ABX9TyOby01VR127ZMS2YWFP8/YE",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
},
"accelerator": "GPU",
"gpuClass": "standard"
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/github/w-okada/voice-changer/blob/dev/VoiceChangerDemo.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"MMVCプレイヤー普通版\n",
"---\n",
"\n",
"このートはColab上でMMVCのボイチェンを行うートです。\n",
"\n",
"正式版はローカルPC上で動かすアプリケーションです。\n",
"\n",
"正式版は、多くの場合より少ないタイムラグで滑らかに音声を変換できます。\n",
"\n",
"詳細な使用方法はこちらの[リポジトリ](https://github.com/w-okada/voice-changer)からご確認ください。\n"
],
"metadata": {
"id": "Lbbmx_Vjl0zo"
}
},
{
"cell_type": "markdown",
"source": [
"# GPUを確認\n",
"GPUを用いたほうが高速に処理が行えます。\n",
"\n",
"下記のコマンドでGPUが確認できない場合は、上のメニューから\n",
"\n",
"「ランタイム」→「ランタイムの変更」→「ハードウェア アクセラレータ」\n",
"\n",
"でGPUを選択してください。"
],
"metadata": {
"id": "oUKi1NYMmXrr"
}
},
{
"cell_type": "code",
"source": [
"# (1) GPUの確認\n",
"!nvidia-smi"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "vV1t7PBRm-o6",
"outputId": "de727091-7c9f-4dbc-a0cc-5985409b289f"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Sun Jan 29 12:09:35 2023 \n",
"+-----------------------------------------------------------------------------+\n",
"| NVIDIA-SMI 510.47.03 Driver Version: 510.47.03 CUDA Version: 11.6 |\n",
"|-------------------------------+----------------------+----------------------+\n",
"| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n",
"| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n",
"| | | MIG M. |\n",
"|===============================+======================+======================|\n",
"| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 |\n",
"| N/A 47C P0 26W / 70W | 0MiB / 15360MiB | 0% Default |\n",
"| | | N/A |\n",
"+-------------------------------+----------------------+----------------------+\n",
" \n",
"+-----------------------------------------------------------------------------+\n",
"| Processes: |\n",
"| GPU GI CI PID Type Process name GPU Memory |\n",
"| ID ID Usage |\n",
"|=============================================================================|\n",
"| No running processes found |\n",
"+-----------------------------------------------------------------------------+\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# 使用するモデルとコンフィグファイルの指定\n",
"\n",
"使用するトレーニング済みのモデルと、トレーニングで使用したコンフィグファイルのパスを指定してください。\n",
"\n",
"多くの場合はGoogle Driveに格納されているファイルを使用すると思います。その場合は、下の(2-2)のセルを実行してドライブをマウントしてください"
],
"metadata": {
"id": "mHvGrgaWnIPA"
}
},
{
"cell_type": "code",
"source": [
"# (2-1) 使用するモデルとコンフィグファイルの指定\n",
"if \"MODEL\" in locals():\n",
" del MODEL\n",
"if \"ONNX\" in locals():\n",
" del ONNX\n",
"\n",
"CONFIG=\"/content/drive/MyDrive/VoiceChanger/config.json\"\n",
"#MODEL=\"/content/drive/MyDrive/VoiceChanger/G_326000.pth\"\n",
"ONNX=\"/content/drive/MyDrive/VoiceChanger/G_326000.onnx\""
],
"metadata": {
"id": "nSXATMWYb4Ik"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "2wxD-gRSMU5R",
"outputId": "4c9da537-a5cb-4c5d-f999-2795b00d41a8"
},
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Mounted at /content/drive\n"
]
}
],
"source": [
"# (2-2) Google Driveのマウント\n",
"from google.colab import drive\n",
"drive.mount('/content/drive')"
]
},
{
"cell_type": "markdown",
"source": [
"# リポジトリのクローン\n",
"リポジトリをクローンします"
],
"metadata": {
"id": "sLBfykjBnjWc"
}
},
{
"cell_type": "code",
"source": [
"# (3) リポジトリのクローン\n",
"!git clone --depth 1 https://github.com/w-okada/voice-changer.git -b v.1.3.7\n",
"%cd voice-changer/server\n",
"!git clone https://github.com/isletennos/MMVC_Client.git\n",
"!cd MMVC_Client && git checkout 04f3fec4fd82dea6657026ec4e1cd80fb29a415c && cd -"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "86wTFmqsNMnD",
"outputId": "2b329892-41b2-4560-e4f0-ab49b4ef83bc"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Cloning into 'voice-changer'...\n",
"remote: Enumerating objects: 157, done.\u001b[K\n",
"remote: Counting objects: 100% (157/157), done.\u001b[K\n",
"remote: Compressing objects: 100% (141/141), done.\u001b[K\n",
"remote: Total 157 (delta 21), reused 70 (delta 5), pack-reused 0\u001b[K\n",
"Receiving objects: 100% (157/157), 1.61 MiB | 4.03 MiB/s, done.\n",
"Resolving deltas: 100% (21/21), done.\n",
"/content/voice-changer/server\n",
"Cloning into 'MMVC_Client'...\n",
"remote: Enumerating objects: 611, done.\u001b[K\n",
"remote: Counting objects: 100% (337/337), done.\u001b[K\n",
"remote: Compressing objects: 100% (120/120), done.\u001b[K\n",
"remote: Total 611 (delta 238), reused 285 (delta 214), pack-reused 274\u001b[K\n",
"Receiving objects: 100% (611/611), 752.38 KiB | 21.50 MiB/s, done.\n",
"Resolving deltas: 100% (360/360), done.\n",
"Note: switching to '04f3fec4fd82dea6657026ec4e1cd80fb29a415c'.\n",
"\n",
"You are in 'detached HEAD' state. You can look around, make experimental\n",
"changes and commit them, and you can discard any commits you make in this\n",
"state without impacting any branches by switching back to a branch.\n",
"\n",
"If you want to create a new branch to retain commits you create, you may\n",
"do so (now or later) by using -c with the switch command. Example:\n",
"\n",
" git switch -c <new-branch-name>\n",
"\n",
"Or undo this operation with:\n",
"\n",
" git switch -\n",
"\n",
"Turn off this advice by setting config variable advice.detachedHead to false\n",
"\n",
"HEAD is now at 04f3fec Merge pull request #30 from Mokuichi147/setupcheck\n",
"/content/voice-changer/server\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# モジュールのインストール\n",
"\n",
"必要なモジュールをインストールします。"
],
"metadata": {
"id": "8Na2PbLZSWgZ"
}
},
{
"cell_type": "code",
"source": [
"# (5) 設定ファイルの確認\n",
"!apt-get install -y libsndfile1-dev &> /dev/null\n",
"!pip install fastapi &> /dev/null\n",
"!pip install pyOpenSSL &> /dev/null\n",
"!pip install python-multipart &> /dev/null\n",
"!pip install python-socketio &> /dev/null\n",
"!pip install uvicorn &> /dev/null\n",
"!pip install websockets &> /dev/null\n",
"!pip install onnxruntime-gpu &> /dev/null"
],
"metadata": {
"id": "LwZAAuqxX7yY"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"# サーバの起動\n",
"\n",
"サーバを起動します。(6-1)\n",
"\n",
"サーバの起動状況を確認します。(6-2) \n",
"\n",
"このセルは繰り返し実行することになるのでCtrl+Retでセルを実行してください。\n",
"\n",
"アクセスできるようになるまで、1~2分かかるようです。コーヒーでも飲みに行きましょう。\n",
"\n",
"下記のようなテキストが表示されたら起動完了です。\n",
"(warningは無視して問題ありません。)\n",
"```\n",
"/usr/local/lib/python3.8/dist-packages/onnxruntime/capi/onnxruntime_inference_collection.py:54: UserWarning: Specified provider 'OpenVINOExecutionProvider' is not in available provider names.Available providers: 'TensorrtExecutionProvider, CUDAExecutionProvider, CPUExecutionProvider'\n",
" warnings.warn(\n",
"/usr/local/lib/python3.8/dist-packages/onnxruntime/capi/onnxruntime_inference_collection.py:54: UserWarning: Specified provider 'DmlExecutionProvider' is not in available provider names.Available providers: 'TensorrtExecutionProvider, CUDAExecutionProvider, CPUExecutionProvider'\n",
" warnings.warn(\n",
"VoiceChanger Initialized (GPU_NUM:1, mps_enabled:False)\n",
" Voice Changerを起動しています。\n",
" -- 設定 -- \n",
" CONFIG:/content/drive/MyDrive/VoiceChanger/config.json, MODEL:None ONNX_MODEL:/content/drive/MyDrive/VoiceChanger/G_326000.onnx\n",
"```\n",
"\n"
],
"metadata": {
"id": "-_2OcN9Borke"
}
},
{
"cell_type": "code",
"source": [
"# (6-1) サーバの起動\n",
"import random\n",
"PORT = 10000 + random.randint(1, 9999)\n",
"LOG_FILE = f\"LOG_FILE_{PORT}\"\n",
"\n",
"if \"MODEL\" in locals() and \"ONNX\" in locals():\n",
" model_param = f\" -m {MODEL} -o {ONNX}\"\n",
"elif \"MODEL\" in locals():\n",
" model_param = f\" -m {MODEL}\"\n",
"elif \"ONNX\" in locals():\n",
" model_param = f\" -o {ONNX}\"\n",
"else:\n",
" model_param = f\"\"\n",
"\n",
"get_ipython().system_raw(f'python3 MMVCServerSIO.py -t MMVC -p {PORT} -c {CONFIG} {model_param} --https False --colab True >{LOG_FILE} 2>&1 &')\n",
"#print(f\"PORT:{PORT}, LOG_FILE:{LOG_FILE}\")"
],
"metadata": {
"id": "iNOAB7zISI6J"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# (6-2) サーバの起動確認 (Ctrl+Retで実行)\n",
"!tail -20 {LOG_FILE}"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "chu06KpAjEK6",
"outputId": "cf0d26f3-66a9-406e-e739-f588483d2851"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"/usr/local/lib/python3.8/dist-packages/onnxruntime/capi/onnxruntime_inference_collection.py:54: UserWarning: Specified provider 'OpenVINOExecutionProvider' is not in available provider names.Available providers: 'TensorrtExecutionProvider, CUDAExecutionProvider, CPUExecutionProvider'\n",
" warnings.warn(\n",
"/usr/local/lib/python3.8/dist-packages/onnxruntime/capi/onnxruntime_inference_collection.py:54: UserWarning: Specified provider 'DmlExecutionProvider' is not in available provider names.Available providers: 'TensorrtExecutionProvider, CUDAExecutionProvider, CPUExecutionProvider'\n",
" warnings.warn(\n",
"VoiceChanger Initialized (GPU_NUM:1, mps_enabled:False)\n",
"\u001b[32m Voice Changerを起動しています。\u001b[0m\n",
"\u001b[34m -- 設定 -- \u001b[0m\n",
"\u001b[34m CONFIG:/content/drive/MyDrive/VoiceChanger/config.json, MODEL:None ONNX_MODEL:/content/drive/MyDrive/VoiceChanger/G_326000.onnx\u001b[0m\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# プロキシを起動\n",
"ウェブサーバへのアクセスをするためのプロキシを起動します。\n",
"\n",
"表示されたURLをクリックして開くと別タブでアプリが開きます。\n",
"\n",
"Colabなので、ロードにある程度時間がかかります(30秒くらい)。"
],
"metadata": {
"id": "WhxcFLQEpctq"
}
},
{
"cell_type": "code",
"source": [
"# (7) プロキシを起動\n",
"from google.colab.output import eval_js\n",
"proxy = eval_js( \"google.colab.kernel.proxyPort(\" + str(PORT) + \")\" )\n",
"print(f\"{proxy}front/?colab=true\")"
],
"metadata": {
"id": "nkRjZm95l87C",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"outputId": "b6ef1db3-37b4-4025-fdbf-711e00646902"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"https://nafdn2dmorn-496ff2e9c6d22116-18341-colab.googleusercontent.com/front/?colab=true\n"
]
}
]
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "Jos5WZHGmz4s"
},
"execution_count": null,
"outputs": []
}
]
}

View File

@ -1,330 +0,0 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"authorship_tag": "ABX9TyNVa3l0Z1DnQzD9WKC/6oWG",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
},
"accelerator": "GPU",
"gpuClass": "standard"
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/github/w-okada/voice-changer/blob/dev/VoiceChangerDemo_Simple.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"MMVCプレイヤー超簡単版\n",
"---\n",
"\n",
"このートはColab上でMMVCのボイチェンを行うートです。\n",
"\n",
"正式版はローカルPC上で動かすアプリケーションです。\n",
"\n",
"正式版は、多くの場合より少ないタイムラグで滑らかに音声を変換できます。\n",
"\n",
"詳細な使用方法はこちらの[リポジトリ](https://github.com/w-okada/voice-changer)からご確認ください。\n"
],
"metadata": {
"id": "Lbbmx_Vjl0zo"
}
},
{
"cell_type": "markdown",
"source": [
"# GPUを確認\n",
"GPUを用いたほうが高速に処理が行えます。\n",
"\n",
"下記のコマンドでGPUが確認できない場合は、上のメニューから\n",
"\n",
"「ランタイム」→「ランタイムの変更」→「ハードウェア アクセラレータ」\n",
"\n",
"でGPUを選択してください。"
],
"metadata": {
"id": "oUKi1NYMmXrr"
}
},
{
"cell_type": "code",
"source": [
"# (1) GPUの確認\n",
"!nvidia-smi"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "vV1t7PBRm-o6",
"outputId": "785be88d-b741-4824-8b99-6863d172fa3b"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Sun Jan 29 12:17:09 2023 \n",
"+-----------------------------------------------------------------------------+\n",
"| NVIDIA-SMI 510.47.03 Driver Version: 510.47.03 CUDA Version: 11.6 |\n",
"|-------------------------------+----------------------+----------------------+\n",
"| GPU Name Persistence-M| Bus-Id Disp.A | Volatile Uncorr. ECC |\n",
"| Fan Temp Perf Pwr:Usage/Cap| Memory-Usage | GPU-Util Compute M. |\n",
"| | | MIG M. |\n",
"|===============================+======================+======================|\n",
"| 0 Tesla T4 Off | 00000000:00:04.0 Off | 0 |\n",
"| N/A 67C P0 30W / 70W | 0MiB / 15360MiB | 0% Default |\n",
"| | | N/A |\n",
"+-------------------------------+----------------------+----------------------+\n",
" \n",
"+-----------------------------------------------------------------------------+\n",
"| Processes: |\n",
"| GPU GI CI PID Type Process name GPU Memory |\n",
"| ID ID Usage |\n",
"|=============================================================================|\n",
"| No running processes found |\n",
"+-----------------------------------------------------------------------------+\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# リポジトリのクローン\n",
"リポジトリをクローンします"
],
"metadata": {
"id": "sLBfykjBnjWc"
}
},
{
"cell_type": "code",
"source": [
"# (2) リポジトリのクローン\n",
"!git clone --depth 1 https://github.com/w-okada/voice-changer.git -b v.1.3.7\n",
"%cd voice-changer/server\n",
"!git clone https://github.com/isletennos/MMVC_Client.git\n",
"!cd MMVC_Client && git checkout 04f3fec4fd82dea6657026ec4e1cd80fb29a415c && cd -\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "86wTFmqsNMnD",
"outputId": "b0bd865c-acec-47e5-b309-ac7fb6906f97"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Cloning into 'voice-changer'...\n",
"remote: Enumerating objects: 157, done.\u001b[K\n",
"remote: Counting objects: 100% (157/157), done.\u001b[K\n",
"remote: Compressing objects: 100% (141/141), done.\u001b[K\n",
"remote: Total 157 (delta 21), reused 70 (delta 5), pack-reused 0\u001b[K\n",
"Receiving objects: 100% (157/157), 1.61 MiB | 1.90 MiB/s, done.\n",
"Resolving deltas: 100% (21/21), done.\n",
"/content/voice-changer/server\n",
"Cloning into 'MMVC_Client'...\n",
"remote: Enumerating objects: 611, done.\u001b[K\n",
"remote: Counting objects: 100% (337/337), done.\u001b[K\n",
"remote: Compressing objects: 100% (120/120), done.\u001b[K\n",
"remote: Total 611 (delta 238), reused 285 (delta 214), pack-reused 274\u001b[K\n",
"Receiving objects: 100% (611/611), 752.38 KiB | 875.00 KiB/s, done.\n",
"Resolving deltas: 100% (360/360), done.\n",
"Note: switching to '04f3fec4fd82dea6657026ec4e1cd80fb29a415c'.\n",
"\n",
"You are in 'detached HEAD' state. You can look around, make experimental\n",
"changes and commit them, and you can discard any commits you make in this\n",
"state without impacting any branches by switching back to a branch.\n",
"\n",
"If you want to create a new branch to retain commits you create, you may\n",
"do so (now or later) by using -c with the switch command. Example:\n",
"\n",
" git switch -c <new-branch-name>\n",
"\n",
"Or undo this operation with:\n",
"\n",
" git switch -\n",
"\n",
"Turn off this advice by setting config variable advice.detachedHead to false\n",
"\n",
"HEAD is now at 04f3fec Merge pull request #30 from Mokuichi147/setupcheck\n",
"/content/voice-changer/server\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# モジュールのインストール\n",
"\n",
"必要なモジュールをインストールします。"
],
"metadata": {
"id": "8Na2PbLZSWgZ"
}
},
{
"cell_type": "code",
"source": [
"# (3) 設定ファイルの確認\n",
"!apt-get install -y libsndfile1-dev &> /dev/null\n",
"!pip install fastapi &> /dev/null\n",
"!pip install pyOpenSSL &> /dev/null\n",
"!pip install python-multipart &> /dev/null\n",
"!pip install python-socketio &> /dev/null\n",
"!pip install uvicorn &> /dev/null\n",
"!pip install websockets &> /dev/null\n",
"!pip install onnxruntime-gpu &> /dev/null\n"
],
"metadata": {
"id": "LwZAAuqxX7yY"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"# サーバの起動\n",
"\n",
"サーバを起動します。(4-1)\n",
"\n",
"サーバの起動状況を確認します。(4-2) \n",
"\n",
"このセルは繰り返し実行することになるのでCtrl+Retでセルを実行してください。\n",
"\n",
"下記のようなテキストが表示されたら起動完了です。\n",
"\n",
"**`DEBUG:asyncio:Using selector: EpollSelector`**\n",
"\n",
"```\n",
" Voice Changerを起動しています。\n",
" -- 設定 -- \n",
" CONFIG:None, MODEL:None ONNX_MODEL:None\n",
"```\n",
"\n"
],
"metadata": {
"id": "-_2OcN9Borke"
}
},
{
"cell_type": "code",
"source": [
"# (4-1) サーバの起動\n",
"import random\n",
"PORT = 10000 + random.randint(1, 9999)\n",
"LOG_FILE = f\"LOG_FILE_{PORT}\"\n",
"\n",
"get_ipython().system_raw(f'python3 MMVCServerSIO.py -t MMVC -p {PORT} --https False --colab True >{LOG_FILE} 2>&1 &')\n",
"#print(f\"PORT:{PORT}, LOG_FILE:{LOG_FILE}\")"
],
"metadata": {
"id": "G-nMdPxEW1rc"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# (4-2) サーバの起動確認\n",
"!sleep 30\n",
"!tail -20 {LOG_FILE}"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "chu06KpAjEK6",
"outputId": "a9cb8947-3902-43ba-c3b9-e5857c0cc7b6"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"VoiceChanger Initialized (GPU_NUM:1, mps_enabled:False)\n",
"\u001b[32m Voice Changerを起動しています。\u001b[0m\n",
"\u001b[34m -- 設定 -- \u001b[0m\n",
"\u001b[34m CONFIG:None, MODEL:None ONNX_MODEL:None\u001b[0m\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# プロキシを起動\n",
"ウェブサーバへのアクセスをするためのプロキシを起動します。\n",
"\n",
"表示されたURLをクリックして開くと別タブでアプリが開きます。\n",
"\n",
"Colabなので、ロードにある程度時間がかかります(30秒くらい)。"
],
"metadata": {
"id": "WhxcFLQEpctq"
}
},
{
"cell_type": "code",
"source": [
"# (5) プロキシを起動\n",
"from google.colab.output import eval_js\n",
"proxy = eval_js( \"google.colab.kernel.proxyPort(\" + str(PORT) + \")\" )\n",
"print(f\"{proxy}front/?colab=true\")"
],
"metadata": {
"id": "nkRjZm95l87C",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"outputId": "5c2ff7a0-f10a-46b9-eaec-96d1874e255f"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"https://xzok7khbzs-496ff2e9c6d22116-13279-colab.googleusercontent.com/front/?colab=true\n"
]
}
]
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "axkt5BjhoiPV"
},
"execution_count": null,
"outputs": []
}
]
}

View File

@ -1,656 +0,0 @@
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {
"colab": {
"provenance": [],
"collapsed_sections": [],
"authorship_tag": "ABX9TyP6Wv8fttT+X2Op6MmqB/kg",
"include_colab_link": true
},
"kernelspec": {
"name": "python3",
"display_name": "Python 3"
},
"language_info": {
"name": "python"
},
"gpuClass": "standard"
},
"cells": [
{
"cell_type": "markdown",
"metadata": {
"id": "view-in-github",
"colab_type": "text"
},
"source": [
"<a href=\"https://colab.research.google.com/github/w-okada/voice-changer/blob/dev/VoiceRecorder.ipynb\" target=\"_parent\"><img src=\"https://colab.research.google.com/assets/colab-badge.svg\" alt=\"Open In Colab\"/></a>"
]
},
{
"cell_type": "markdown",
"source": [
"Voice Recorder\n",
"---\n",
"\n",
"このートでは、MMVCのトレーニング用の音声を録画するアプリ\"Voice Recorder\"をColab上から起動します。\n",
"\n",
"録音された音声はこのートを通してGoogle Drive上にアップロードすることができます。\n",
"\n",
"また、従来のVoice Recorderと同様にローカルPCにダウンロードすることもできます。\n",
"\n",
"録音後にブラウザとcolab上のサーバ間でやり取りを行うので、更新に少しタイムラグが発生します。\n",
"\n",
"ご自身のPCでトレーニングを行う予定の場合は、colab上のサーバで録音するメリットはほぼありませんので、より快適な録音をするために[こちらのgithub上のVoice Recorder](https://w-okada.github.io/voice-changer/)をご使用ください。\n",
"\n",
"\n",
"より詳細な情報はこちらの[リポジトリ](https://github.com/w-okada/voice-changer)からご確認いただけます。\n"
],
"metadata": {
"id": "Lbbmx_Vjl0zo"
}
},
{
"cell_type": "markdown",
"source": [
"# 録音データを格納するフォルダを指定\n",
"\n",
"フォルダは次の二つを指定する必要があります。\n",
"1. 録音アプリ用のキャッシュデータ格納フォルダ\n",
"2. トレーニングデータの格納フォルダ\n",
"\n",
"通常、録音データはGoogle Drive上のフォルダに格納すると思います。\n",
"\n",
"まずは(1-1)を実行してドライブをマウントしてください。\n",
"\n",
"その後、(1-2)で上記の格納フォルダを指定してください。"
],
"metadata": {
"id": "mHvGrgaWnIPA"
}
},
{
"cell_type": "code",
"source": [
"# (1-1) Google Driveのマウント\n",
"from google.colab import drive\n",
"drive.mount('/content/drive')"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "Eihm8H2X-7wm",
"outputId": "76331fb1-5ef8-40e6-a381-258b5e425853"
},
"execution_count": 1,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Mounted at /content/drive\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"# (1-2) 使用するモデルとコンフィグファイルの指定\n",
"RECORDER_DATA_DIR=\"/content/drive/MyDrive/VoiceChanger/voice_data\"\n",
"MMVC_DATA_DIR=\"/content/drive/MyDrive/VoiceChanger/dataset\"\n"
],
"metadata": {
"id": "nSXATMWYb4Ik"
},
"execution_count": 2,
"outputs": []
},
{
"cell_type": "markdown",
"source": [
"# リポジトリのクローン\n",
"リポジトリをクローンします"
],
"metadata": {
"id": "sLBfykjBnjWc"
}
},
{
"cell_type": "code",
"source": [
"# (2) リポジトリのクローン\n",
"!git clone --depth 1 https://github.com/w-okada/voice-changer.git -b ver_1.0\n",
"%cd voice-changer/docs/\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "86wTFmqsNMnD",
"outputId": "40471833-d720-41c9-f4a7-ac15fbf18e14"
},
"execution_count": 3,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Cloning into 'voice-changer'...\n",
"remote: Enumerating objects: 81, done.\u001b[K\n",
"remote: Counting objects: 100% (81/81), done.\u001b[K\n",
"remote: Compressing objects: 100% (68/68), done.\u001b[K\n",
"remote: Total 81 (delta 12), reused 53 (delta 5), pack-reused 0\u001b[K\n",
"Unpacking objects: 100% (81/81), done.\n",
"Note: checking out 'f8823cb7e2025f13227f5918408cceda224bf9f0'.\n",
"\n",
"You are in 'detached HEAD' state. You can look around, make experimental\n",
"changes and commit them, and you can discard any commits you make in this\n",
"state without impacting any branches by performing another checkout.\n",
"\n",
"If you want to create a new branch to retain commits you create, you may\n",
"do so (now or later) by using -b with the checkout command again. Example:\n",
"\n",
" git checkout -b <new-branch-name>\n",
"\n",
"/content/voice-changer/docs\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# ファイルの配置\n",
"アプリケーションの挙動を記した設定ファイルをコピーします(3-1)。(3-2)はコピーした設定ファイルを表示しています。もしかしたらうまく動かないときに役立つかもしれません。"
],
"metadata": {
"id": "jmDY8W_fnuSi"
}
},
{
"cell_type": "code",
"source": [
"# (3-1) 設定ファイルのコピー\n",
"!cp ../template/setting_recorder_colab.json assets/setting.json"
],
"metadata": {
"id": "ow88ZaubluOJ"
},
"execution_count": 4,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# (3-2) 設定ファイルの内容確認\n",
"\n",
"!cat assets/setting.json"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "rpWUobjlBCNF",
"outputId": "285e0259-16af-4932-e78b-bec94f337e9c"
},
"execution_count": 5,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"{\n",
" \"app_title\": \"voice-recorder\",\n",
" \"storage_type\":\"server\",\n",
" \"use_mel_spectrogram\":true,\n",
" \"text\": [\n",
" {\n",
" \"title\": \"ITA-emotion\",\n",
" \"wavPrefix\": \"emotion\",\n",
" \"file\": \"./assets/text/ITA_emotion_all.txt\",\n",
" \"file_hira\": \"./assets/text/ITA_emotion_all_hira.txt\"\n",
" },\n",
" {\n",
" \"title\": \"ITA-recitation\",\n",
" \"wavPrefix\": \"recitation\",\n",
" \"file\": \"./assets/text/ITA_recitation_all.txt\",\n",
" \"file_hira\": \"./assets/text/ITA_recitation_all_hira.txt\"\n",
" },\n",
" {\n",
" \"title\": \"wagahaiwa\",\n",
" \"wavPrefix\": \"wagahaiwa\",\n",
" \"file\": \"./assets/text/wagahaiwa.txt\",\n",
" \"file_hira\": \"./assets/text/wagahaiwa_hira.txt\"\n",
" }\n",
" ]\n",
"}\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# モジュールのインストール\n",
"\n",
"必要なモジュールをインストールします。"
],
"metadata": {
"id": "8Na2PbLZSWgZ"
}
},
{
"cell_type": "code",
"source": [
"# (4) 設定ファイルの確認\n",
"!pip install flask\n",
"!pip install flask_cors\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "LwZAAuqxX7yY",
"outputId": "ea2b3b39-d571-4d47-a38b-d0e657a335cd"
},
"execution_count": 6,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
"Requirement already satisfied: flask in /usr/local/lib/python3.7/dist-packages (1.1.4)\n",
"Requirement already satisfied: Jinja2<3.0,>=2.10.1 in /usr/local/lib/python3.7/dist-packages (from flask) (2.11.3)\n",
"Requirement already satisfied: Werkzeug<2.0,>=0.15 in /usr/local/lib/python3.7/dist-packages (from flask) (1.0.1)\n",
"Requirement already satisfied: click<8.0,>=5.1 in /usr/local/lib/python3.7/dist-packages (from flask) (7.1.2)\n",
"Requirement already satisfied: itsdangerous<2.0,>=0.24 in /usr/local/lib/python3.7/dist-packages (from flask) (1.1.0)\n",
"Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from Jinja2<3.0,>=2.10.1->flask) (2.0.1)\n",
"Looking in indexes: https://pypi.org/simple, https://us-python.pkg.dev/colab-wheels/public/simple/\n",
"Collecting flask_cors\n",
" Downloading Flask_Cors-3.0.10-py2.py3-none-any.whl (14 kB)\n",
"Requirement already satisfied: Six in /usr/local/lib/python3.7/dist-packages (from flask_cors) (1.15.0)\n",
"Requirement already satisfied: Flask>=0.9 in /usr/local/lib/python3.7/dist-packages (from flask_cors) (1.1.4)\n",
"Requirement already satisfied: Werkzeug<2.0,>=0.15 in /usr/local/lib/python3.7/dist-packages (from Flask>=0.9->flask_cors) (1.0.1)\n",
"Requirement already satisfied: itsdangerous<2.0,>=0.24 in /usr/local/lib/python3.7/dist-packages (from Flask>=0.9->flask_cors) (1.1.0)\n",
"Requirement already satisfied: Jinja2<3.0,>=2.10.1 in /usr/local/lib/python3.7/dist-packages (from Flask>=0.9->flask_cors) (2.11.3)\n",
"Requirement already satisfied: click<8.0,>=5.1 in /usr/local/lib/python3.7/dist-packages (from Flask>=0.9->flask_cors) (7.1.2)\n",
"Requirement already satisfied: MarkupSafe>=0.23 in /usr/local/lib/python3.7/dist-packages (from Jinja2<3.0,>=2.10.1->Flask>=0.9->flask_cors) (2.0.1)\n",
"Installing collected packages: flask-cors\n",
"Successfully installed flask-cors-3.0.10\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# サーバの起動\n",
"\n",
"サーバを起動します。(5-1) \n",
"\n",
"サーバの起動状況を確認します。(5-2) \n",
"\n",
"このセルは繰り返し実行することになるのでCtrl+Retでセルを実行してください。\n",
"\n",
"アクセスできるようになるまで、数秒かかります。\n",
"\n",
"下記のようなテキストが表示されたら起動完了です。\n",
"\n",
"```\n",
"[2022-09-13 22:20:49,936] INFO in recorderServer: START APP\n",
" * Serving Flask app \"recorderServer\" (lazy loading)\n",
" * Environment: production\n",
" WARNING: This is a development server. Do not use it in a production deployment.\n",
" Use a production WSGI server instead.\n",
" * Debug mode: on\n",
"[2022-09-13 22:20:49,946] INFO in _internal: * Running on http://0.0.0.0:8018/ (Press CTRL+C to quit)\n",
"[2022-09-13 22:20:49,947] INFO in _internal: * Restarting with stat\n",
"[2022-09-13 22:20:50,166] INFO in recorderServer: START APP\n",
"[2022-09-13 22:20:50,174] WARNING in _internal: * Debugger is active!\n",
"[2022-09-13 22:20:50,200] INFO in _internal: * Debugger PIN: 334-166-753\n",
"```\n",
"\n"
],
"metadata": {
"id": "-_2OcN9Borke"
}
},
{
"cell_type": "code",
"source": [
"# (5-1) サーバの起動\n",
"PORT=8018\n",
"get_ipython().system_raw(f'python3 recorderServer.py {PORT} {RECORDER_DATA_DIR} >foo 2>&1 &')"
],
"metadata": {
"id": "iNOAB7zISI6J"
},
"execution_count": 7,
"outputs": []
},
{
"cell_type": "code",
"source": [
"# (5-2) サーバの起動確認 (Ctrl+Retで実行)\n",
"!cat foo"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "chu06KpAjEK6",
"outputId": "6f5cbed9-65a7-4570-f58a-5447e402947c"
},
"execution_count": 10,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"[2022-11-08 19:11:17,679] INFO in recorderServer: START APP\n",
" * Serving Flask app \"recorderServer\" (lazy loading)\n",
" * Environment: production\n",
" WARNING: This is a development server. Do not use it in a production deployment.\n",
" Use a production WSGI server instead.\n",
" * Debug mode: on\n",
"[2022-11-08 19:11:17,696] INFO in _internal: * Running on http://0.0.0.0:8018/ (Press CTRL+C to quit)\n",
"[2022-11-08 19:11:17,697] INFO in _internal: * Restarting with stat\n",
"[2022-11-08 19:11:17,893] INFO in recorderServer: START APP\n",
"[2022-11-08 19:11:17,900] WARNING in _internal: * Debugger is active!\n",
"[2022-11-08 19:11:17,930] INFO in _internal: * Debugger PIN: 225-344-519\n"
]
}
]
},
{
"cell_type": "markdown",
"source": [
"# プロキシを起動\n",
"ウェブサーバへのアクセスをするためのプロキシを起動します。\n",
"\n",
"表示されたURLをクリックして開くと別タブでアプリが開きます。\n",
"\n",
"Colabなので、ロードにある程度時間がかかります(30秒くらい)。"
],
"metadata": {
"id": "WhxcFLQEpctq"
}
},
{
"cell_type": "code",
"source": [
"# (7) プロキシを起動\n",
"from google.colab import output\n",
"\n",
"output.serve_kernel_port_as_window(PORT)"
],
"metadata": {
"id": "nkRjZm95l87C",
"colab": {
"base_uri": "https://localhost:8080/",
"height": 34
},
"outputId": "7d2664b8-945c-4ee6-b49f-51f7f96cf388"
},
"execution_count": 11,
"outputs": [
{
"output_type": "display_data",
"data": {
"text/plain": [
"<IPython.core.display.Javascript object>"
],
"application/javascript": [
"(async (port, path, text, element) => {\n",
" if (!google.colab.kernel.accessAllowed) {\n",
" return;\n",
" }\n",
" element.appendChild(document.createTextNode(''));\n",
" const url = await google.colab.kernel.proxyPort(port);\n",
" const anchor = document.createElement('a');\n",
" anchor.href = new URL(path, url).toString();\n",
" anchor.target = '_blank';\n",
" anchor.setAttribute('data-href', url + path);\n",
" anchor.textContent = text;\n",
" element.appendChild(anchor);\n",
" })(8018, \"/\", \"https://localhost:8018/\", window.element)"
]
},
"metadata": {}
}
]
},
{
"cell_type": "markdown",
"source": [
"# トレーニング用データフォルダ\n",
"\n",
"以下、トレーニング用のフォルダを作成します。\n",
"\n",
"\n"
],
"metadata": {
"id": "ZGuYYN7oCSM4"
}
},
{
"cell_type": "code",
"source": [
"corpus_id = \"14oXoQqLxRkP8NJK8qMYGee1_q2uEED1z\"\n",
"\n",
"data_setting = [\n",
" [\"user\", \"\", \"\", \"00_myvoice\", \"107\"],\n",
" [\"zundamon\", \"1h8Ajyvoig7Hl3LSSt2vYX0sUHX3JDF3R\", \"1205_zundamon\", \"01_target_zundamon\", \"100\"],\n",
" [\"tsumugi\", \"14zE0F_5ZCQWXf6m6SUPF5Y3gpL6yb7zk\", \"344_tsumugi\", \"02_target_tsumugi\", \"103\"],\n",
" [\"metan\", \"1iCrpzhqXm-0YdktOPM8M1pMtgQIDF3r4\", \"459_methane\", \"03_target_metan\", \"102\"],\n",
" [\"sora\", \"1MXfMRG_sjbsaLihm7wEASG2PwuCponZF\", \"912_sora\", \"04_target_ksora\", \"101\"],\n",
"]"
],
"metadata": {
"id": "3PhrmCD2LaCH"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"import os, glob\n",
"\n",
"os.makedirs(MMVC_DATA_DIR, exist_ok=True)\n",
"speaker_list = os.path.join(MMVC_DATA_DIR, \"multi_speaker_correspondence.txt\")\n",
"!echo \"00_myvoice|107\" > {speaker_list}\n",
"!echo \"01_target_zundamon|100\" >> {speaker_list}\n",
"!echo \"02_target_tsumugi|103\" >> {speaker_list}\n",
"!echo \"03_target_metan|102\" >> {speaker_list}\n",
"!echo \"04_target_ksora|101\" >> {speaker_list}\n",
"\n",
"!cat {speaker_list}\n",
"\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "f5l6ggSyACLs",
"outputId": "4db3571a-46e6-4fd9-c560-628cf4af9284"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stdout",
"text": [
"00_myvoice|107\n",
"01_target_zundamon|100\n",
"02_target_tsumugi|103\n",
"03_target_metan|102\n",
"04_target_ksora|101\n"
]
}
]
},
{
"cell_type": "code",
"source": [
"!rm -rf /content/drive/MyDrive/VoiceChanger/train_data/00_myvoice/wav/*"
],
"metadata": {
"id": "UEVb2GGZSesY"
},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"source": [
"import gdown\n",
"\n",
"gdown.download(f'https://drive.google.com/uc?id={corpus_id}', f'ita_corpus.zip', quiet=False)\n",
"!unzip -oq 'ita_corpus.zip'\n",
"\n",
"for chara in data_setting:\n",
" chara_root_dir = os.path.join(MMVC_DATA_DIR, chara[3])\n",
" os.makedirs(chara_root_dir, exist_ok=True)\n",
" \n",
" chara_text_dir = os.path.join(chara_root_dir, \"text\")\n",
" os.makedirs(chara_text_dir, exist_ok=True)\n",
" chara_wav_dir = os.path.join(chara_root_dir, \"wav\")\n",
" os.makedirs(chara_wav_dir, exist_ok=True)\n",
"\n",
" if chara[0] != \"user\":\n",
" gdown.download(f'https://drive.google.com/uc?id={chara[1]}', f'{chara[0]}.zip', quiet=False)\n",
" !unzip -f '{chara[0]}.zip'\n",
" !cp -rf {chara[2]}/* {chara_root_dir}/\n",
"\n",
" if chara[0] == \"user\":\n",
" !cp MMVC向けITAコーパス文章ファイル_配布用/ITA_emotion_hira_100file/* {chara_text_dir}\n",
" !cp MMVC向けITAコーパス文章ファイル_配布用/ITA_recitation_hira_324file/* {chara_text_dir}\n",
"\n",
" file_list = [os.path.abspath(p) for p in glob.glob(f\"{RECORDER_DATA_DIR}/*/*.zip\")]\n",
" for f in list(file_list):\n",
" # print(f)\n",
" basename = os.path.basename(f)\n",
" wavname = os.path.splitext(basename)[0] + \".wav\"\n",
" full_path = os.path.join(chara_wav_dir, wavname)\n",
" # print(basename, wavname, full_path)\n",
" !unzip -oq {f} vf24kTrim.wav\n",
" !cp vf24kTrim.wav {full_path}\n",
"\n",
"\n",
"\n",
"\n"
],
"metadata": {
"colab": {
"base_uri": "https://localhost:8080/"
},
"id": "L8UsVp3dDs4R",
"outputId": "5d640caf-87b0-45a6-aa0c-76295e537f6a"
},
"execution_count": null,
"outputs": [
{
"output_type": "stream",
"name": "stderr",
"text": [
"Downloading...\n",
"From: https://drive.google.com/uc?id=14oXoQqLxRkP8NJK8qMYGee1_q2uEED1z\n",
"To: /content/voice-changer/docs/ita_corpus.zip\n",
"100%|██████████| 1.20M/1.20M [00:00<00:00, 87.9MB/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"/content/drive/MyDrive/VoiceChanger/voice_data/ITA-emotion/emotion000.zip\n",
"/content/drive/MyDrive/VoiceChanger/voice_data/ITA-emotion/emotion002.zip\n",
"/content/drive/MyDrive/VoiceChanger/voice_data/ITA-emotion/emotion001.zip\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Downloading...\n",
"From: https://drive.google.com/uc?id=1h8Ajyvoig7Hl3LSSt2vYX0sUHX3JDF3R\n",
"To: /content/voice-changer/docs/zundamon.zip\n",
"100%|██████████| 55.6M/55.6M [00:00<00:00, 251MB/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Archive: zundamon.zip\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Downloading...\n",
"From: https://drive.google.com/uc?id=14zE0F_5ZCQWXf6m6SUPF5Y3gpL6yb7zk\n",
"To: /content/voice-changer/docs/tsumugi.zip\n",
"100%|██████████| 73.0M/73.0M [00:00<00:00, 226MB/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Archive: tsumugi.zip\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Downloading...\n",
"From: https://drive.google.com/uc?id=1iCrpzhqXm-0YdktOPM8M1pMtgQIDF3r4\n",
"To: /content/voice-changer/docs/metan.zip\n",
"100%|██████████| 51.8M/51.8M [00:00<00:00, 219MB/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Archive: metan.zip\n"
]
},
{
"output_type": "stream",
"name": "stderr",
"text": [
"Downloading...\n",
"From: https://drive.google.com/uc?id=1MXfMRG_sjbsaLihm7wEASG2PwuCponZF\n",
"To: /content/voice-changer/docs/sora.zip\n",
"100%|██████████| 70.2M/70.2M [00:00<00:00, 184MB/s]\n"
]
},
{
"output_type": "stream",
"name": "stdout",
"text": [
"Archive: sora.zip\n"
]
}
]
},
{
"cell_type": "code",
"source": [],
"metadata": {
"id": "yHmaXx31EOta"
},
"execution_count": null,
"outputs": []
}
]
}

11
client/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"workbench.colorCustomizations": {
"tab.activeBackground": "#65952acc"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.printWidth": 1024,
"prettier.tabWidth": 4,
"files.associations": {
"*.css": "postcss"
}
}

View File

@ -1,8 +1,11 @@
{
"files.associations": {
"*.css": "postcss"
},
"workbench.colorCustomizations": {
"tab.activeBackground": "#65952acc"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.printWidth": 1024,
"prettier.tabWidth": 4,
"files.associations": {
"*.css": "postcss"
}
}

View File

@ -0,0 +1,11 @@
# cd ~/git-work/voice-changer-js/lib/ ; npm run build:dev; cd -
# rm -rf node_modules/@dannadori/voice-changer-js
# mkdir -p node_modules/@dannadori/voice-changer-js/dist
# cp -r ~/git-work/voice-changer-js/lib/package.json node_modules/@dannadori/voice-changer-js/
# cp -r ~/git-work/voice-changer-js/lib/dist node_modules/@dannadori/voice-changer-js/
cd ~/git-work/voice-changer-js/lib/ ; npm run build:prod; cd -
rm -rf node_modules/@dannadori/voice-changer-js
mkdir -p node_modules/@dannadori/voice-changer-js/dist
cp -r ~/git-work/voice-changer-js/lib/package.json node_modules/@dannadori/voice-changer-js/
cp -r ~/git-work/voice-changer-js/lib/dist node_modules/@dannadori/voice-changer-js/

View File

@ -0,0 +1,928 @@
<?xml version='1.0' encoding='utf-8'?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:ns2="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="100 60 420 450" version="1.1">
<metadata>
<rdf:RDF>
<ns2:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2023-11-19T11:21:56.358384</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<ns2:Agent>
<dc:title>Matplotlib v3.7.1, https://matplotlib.org/</dc:title>
</ns2:Agent>
</dc:creator>
</ns2:Work>
</rdf:RDF>
</metadata>
<defs>
<style type="text/css">
* {
stroke-linejoin: round;
stroke-linecap: butt
}
</style>
<style type="text/css">
.beatrice-node-pointer {
cursor: pointer;
}
.beatrice-node-pointer:hover {
stroke: gray;
}
.beatrice-node-pointer-selected {
stroke: #ef6767c2;
stroke-width: 3
}
.beatrice-text-pointer {
cursor: pointer;
pointer-events: none
}
.beatrice-text-pointer:hover {
/* ホバー時のスタイルは既に設定されたスタイルと異なる特定の属性を変更することができます。 */
}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 576 L 576 576 L 576 0 L 0 0 z " style="fill: #ffffff" />
</g>
<g id="axes_1">
<g id="LineCollection_1">
<path d="M 403.96157 149.258085 L 366.630583 148.159991 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 396.547407 371.476481 L 372.120414 365.421971 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 396.547407 371.476481 L 416.760989 346.999139 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 396.547407 371.476481 L 404.238335 402.754731 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 258.035169 326.134244 L 298.859694 332.465911 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 167.453327 366.897955 L 203.987537 347.931194 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 436.352807 416.173738 L 404.238335 402.754731 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 391.514336 242.048236 L 417.560846 259.464346 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 391.514336 242.048236 L 355.734145 235.68791 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 391.514336 242.048236 L 424.070309 219.021704 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 205.541044 459.711101 L 230.303076 436.148139 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 160.44225 292.540336 L 167.396334 325.961848 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 345.679012 107.607273 L 366.630583 148.159991 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 325.345004 219.195921 L 355.734145 235.68791 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 325.345004 219.195921 L 297.530501 194.55124 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 363.075301 201.701937 L 355.734145 235.68791 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 363.075301 201.701937 L 341.462109 170.414842 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 366.630583 148.159991 L 341.462109 170.414842 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 167.396334 325.961848 L 203.987537 347.931194 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 262.111309 181.887977 L 297.530501 194.55124 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 189.293496 262.735141 L 222.122563 261.416721 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 277.95603 462.622539 L 293.230217 421.393405 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 333.932593 269.342364 L 301.9174 258.124913 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 333.932593 269.342364 L 355.734145 235.68791 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 334.666605 338.097578 L 320.07566 368.578481 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 203.987537 347.931194 L 242.811958 354.082183 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 288.059985 363.924972 L 320.07566 368.578481 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 288.059985 363.924972 L 276.198518 388.99868 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 288.059985 363.924972 L 298.859694 332.465911 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 288.059985 363.924972 L 242.811958 354.082183 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 276.198518 388.99868 L 293.230217 421.393405 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 293.230217 421.393405 L 309.530924 454.827332 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 293.230217 421.393405 L 260.004712 426.426278 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 423.853712 378.321354 L 404.238335 402.754731 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 205.214352 217.066163 L 222.122563 261.416721 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 154.047193 423.153273 L 193.933786 408.004355 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 298.859694 332.465911 L 277.79477 306.980241 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 277.79477 306.980241 L 282.261978 282.779534 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 260.004712 426.426278 L 230.303076 436.148139 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 260.004712 426.426278 L 228.689744 409.959215 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 260.004712 426.426278 L 261.506403 474.152727 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 301.9174 258.124913 L 282.261978 282.779534 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 254.897329 263.033159 L 282.261978 282.779534 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 254.897329 263.033159 L 222.122563 261.416721 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 321.267463 403.021207 L 320.07566 368.578481 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 345.750267 380.131711 L 320.07566 368.578481 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 345.750267 380.131711 L 372.120414 365.421971 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 345.750267 380.131711 L 351.226176 419.342667 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 404.238335 402.754731 L 400.607869 434.730447 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 193.933786 408.004355 L 230.303076 436.148139 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
</g>
<g id="PathCollection_1">
<defs>
<path id="C0_0_b0ffb3bf4a"
d="M 0 11.18034 C 2.965061 11.18034 5.80908 10.002309 7.905694 7.905694 C 10.002309 5.80908 11.18034 2.965061 11.18034 -0 C 11.18034 -2.965061 10.002309 -5.80908 7.905694 -7.905694 C 5.80908 -10.002309 2.965061 -11.18034 0 -11.18034 C -2.965061 -11.18034 -5.80908 -10.002309 -7.905694 -7.905694 C -10.002309 -5.80908 -11.18034 -2.965061 -11.18034 0 C -11.18034 2.965061 -10.002309 5.80908 -7.905694 7.905694 C -5.80908 10.002309 -2.965061 11.18034 0 11.18034 z " />
</defs>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-0"
onclick="(()=&gt;{console.log('node 0')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="403.96157" y="149.258085" style="fill: #e7f5d2" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-1"
onclick="(()=&gt;{console.log('node 1')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="396.547407" y="371.476481" style="fill: #fbe8f2" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-2"
onclick="(()=&gt;{console.log('node 2')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="258.035169" y="326.134244" style="fill: #cfebaa" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-3"
onclick="(()=&gt;{console.log('node 3')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="167.453327" y="366.897955" style="fill: #f1f6e8" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-4"
onclick="(()=&gt;{console.log('node 4')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="436.352807" y="416.173738" style="fill: #e89ac6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-5"
onclick="(()=&gt;{console.log('node 5')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="391.514336" y="242.048236" style="fill: #f3bcdd" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-6"
onclick="(()=&gt;{console.log('node 6')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="205.541044" y="459.711101" style="fill: #fbd9ec" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-7"
onclick="(()=&gt;{console.log('node 7')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="160.44225" y="292.540336" style="fill: #9ed067" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-8"
onclick="(()=&gt;{console.log('node 8')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="424.070309" y="219.021704" style="fill: #e1f3c7" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-9"
onclick="(()=&gt;{console.log('node 9')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="345.679012" y="107.607273" style="fill: #d0ecad" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-10"
onclick="(()=&gt;{console.log('node 10')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="325.345004" y="219.195921" style="fill: #eff6e4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-11"
onclick="(()=&gt;{console.log('node 11')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="363.075301" y="201.701937" style="fill: #f9f0f5" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-12"
onclick="(()=&gt;{console.log('node 12')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="366.630583" y="148.159991" style="fill: #ebf6dc" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-13"
onclick="(()=&gt;{console.log('node 13')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="341.462109" y="170.414842" style="fill: #fad6ea" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-14"
onclick="(()=&gt;{console.log('node 14')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="167.396334" y="325.961848" style="fill: #f5f7f3" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-15"
onclick="(()=&gt;{console.log('node 15')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="262.111309" y="181.887977" style="fill: #e9f5d6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-16"
onclick="(()=&gt;{console.log('node 16')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="189.293496" y="262.735141" style="fill: #fce5f1" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-17"
onclick="(()=&gt;{console.log('node 17')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="277.95603" y="462.622539" style="fill: #c4e699" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-18"
onclick="(()=&gt;{console.log('node 18')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="333.932593" y="269.342364" style="fill: #f8f4f6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-19"
onclick="(()=&gt;{console.log('node 19')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="416.760989" y="346.999139" style="fill: #eef6e2" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-20"
onclick="(()=&gt;{console.log('node 20')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="334.666605" y="338.097578" style="fill: #f5f7f3" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-21"
onclick="(()=&gt;{console.log('node 21')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="203.987537" y="347.931194" style="fill: #edf6df" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-22"
onclick="(()=&gt;{console.log('node 22')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="288.059985" y="363.924972" style="fill: #ddf1c1" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-23"
onclick="(()=&gt;{console.log('node 23')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="276.198518" y="388.99868" style="fill: #f5f7f3" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-24"
onclick="(()=&gt;{console.log('node 24')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="293.230217" y="421.393405" style="fill: #f3f7ef" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-25"
onclick="(()=&gt;{console.log('node 25')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="423.853712" y="378.321354" style="fill: #edf6df" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-26"
onclick="(()=&gt;{console.log('node 26')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="205.214352" y="217.066163" style="fill: #e7f5d2" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-27"
onclick="(()=&gt;{console.log('node 27')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="242.811958" y="354.082183" style="fill: #d2ecb0" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-28"
onclick="(()=&gt;{console.log('node 28')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="154.047193" y="423.153273" style="fill: #e6f5d0" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-29"
onclick="(()=&gt;{console.log('node 29')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="298.859694" y="332.465911" style="fill: #ecf6de" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-30"
onclick="(()=&gt;{console.log('node 30')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="277.79477" y="306.980241" style="fill: #eaf5d9" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-31"
onclick="(()=&gt;{console.log('node 31')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="260.004712" y="426.426278" style="fill: #f9f1f5" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-32"
onclick="(()=&gt;{console.log('node 32')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="301.9174" y="258.124913" style="fill: #dbf0bf" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-33"
onclick="(()=&gt;{console.log('node 33')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="254.897329" y="263.033159" style="fill: #eff6e4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-34"
onclick="(()=&gt;{console.log('node 34')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="321.267463" y="403.021207" style="fill: #d0ecad" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-35"
onclick="(()=&gt;{console.log('node 35')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="345.750267" y="380.131711" style="fill: #f9eef4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-36"
onclick="(()=&gt;{console.log('node 36')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="404.238335" y="402.754731" style="fill: #f9eef4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-37"
onclick="(()=&gt;{console.log('node 37')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="355.734145" y="235.68791" style="fill: #f9eef4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-38"
onclick="(()=&gt;{console.log('node 38')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="193.933786" y="408.004355" style="fill: #f0f6e7" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-39"
onclick="(()=&gt;{console.log('node 39')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="297.530501" y="194.55124" style="fill: #f3f6ed" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-40"
onclick="(()=&gt;{console.log('node 40')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="320.07566" y="368.578481" style="fill: #dbf0bf" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-41"
onclick="(()=&gt;{console.log('node 41')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="228.689744" y="409.959215" style="fill: #f9eff4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-42"
onclick="(()=&gt;{console.log('node 42')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="351.226176" y="419.342667" style="fill: #cfebaa" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-43"
onclick="(()=&gt;{console.log('node 43')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="372.120414" y="365.421971" style="fill: #f7f6f7" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-44"
onclick="(()=&gt;{console.log('node 44')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="230.303076" y="436.148139" style="fill: #f8cee6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-45"
onclick="(()=&gt;{console.log('node 45')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="261.506403" y="474.152727" style="fill: #e6f5d0" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-46"
onclick="(()=&gt;{console.log('node 46')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="417.560846" y="259.464346" style="fill: #b7e085" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-47"
onclick="(()=&gt;{console.log('node 47')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="400.607869" y="434.730447" style="fill: #f8cee6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-48"
onclick="(()=&gt;{console.log('node 48')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="282.261978" y="282.779534" style="fill: #d6eeb6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-49"
onclick="(()=&gt;{console.log('node 49')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="222.122563" y="261.416721" style="fill: #edf6df" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-50"
onclick="(()=&gt;{console.log('node 50')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="309.530924" y="454.827332" style="fill: #f9eef4" />
</g>
</g>
<g id="beatrice-text-female-0" onclick="(()=&gt;{console.log('text 0 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(399.786883 152.569335) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-32"
d="M 1844 884 L 3897 884 L 3897 0 L 506 0 L 506 884 L 2209 2388 Q 2438 2594 2547 2791 Q 2656 2988 2656 3200 Q 2656 3528 2436 3728 Q 2216 3928 1850 3928 Q 1569 3928 1234 3808 Q 900 3688 519 3450 L 519 4475 Q 925 4609 1322 4679 Q 1719 4750 2100 4750 Q 2938 4750 3402 4381 Q 3866 4013 3866 3353 Q 3866 2972 3669 2642 Q 3472 2313 2841 1759 L 1844 884 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-32" />
</g>
</g>
</g>
<g id="beatrice-text-female-1" onclick="(()=&gt;{console.log('text 1 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(392.37272 374.787731) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-34"
d="M 2356 3675 L 1038 1722 L 2356 1722 L 2356 3675 z M 2156 4666 L 3494 4666 L 3494 1722 L 4159 1722 L 4159 850 L 3494 850 L 3494 0 L 2356 0 L 2356 850 L 288 850 L 288 1881 L 2156 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-34" />
</g>
</g>
</g>
<g id="beatrice-text-female-2" onclick="(()=&gt;{console.log('text 2 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(253.860482 329.445494) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-37"
d="M 428 4666 L 3944 4666 L 3944 3988 L 2125 0 L 953 0 L 2675 3781 L 428 3781 L 428 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-37" />
</g>
</g>
</g>
<g id="beatrice-text-female-3" onclick="(()=&gt;{console.log('text 3 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(163.27864 370.209205) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-38"
d="M 2228 2088 Q 1891 2088 1709 1903 Q 1528 1719 1528 1375 Q 1528 1031 1709 848 Q 1891 666 2228 666 Q 2563 666 2741 848 Q 2919 1031 2919 1375 Q 2919 1722 2741 1905 Q 2563 2088 2228 2088 z M 1350 2484 Q 925 2613 709 2878 Q 494 3144 494 3541 Q 494 4131 934 4440 Q 1375 4750 2228 4750 Q 3075 4750 3515 4442 Q 3956 4134 3956 3541 Q 3956 3144 3739 2878 Q 3522 2613 3097 2484 Q 3572 2353 3814 2058 Q 4056 1763 4056 1313 Q 4056 619 3595 264 Q 3134 -91 2228 -91 Q 1319 -91 855 264 Q 391 619 391 1313 Q 391 1763 633 2058 Q 875 2353 1350 2484 z M 1631 3419 Q 1631 3141 1786 2991 Q 1941 2841 2228 2841 Q 2509 2841 2662 2991 Q 2816 3141 2816 3419 Q 2816 3697 2662 3845 Q 2509 3994 2228 3994 Q 1941 3994 1786 3844 Q 1631 3694 1631 3419 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-38" />
</g>
</g>
</g>
<g id="beatrice-text-female-4" onclick="(()=&gt;{console.log('text 4 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(428.003432 419.484988) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-31"
d="M 750 831 L 1813 831 L 1813 3847 L 722 3622 L 722 4441 L 1806 4666 L 2950 4666 L 2950 831 L 4013 831 L 4013 0 L 750 0 L 750 831 z "
transform="scale(0.015625)" />
<path id="DejaVuSans-Bold-30"
d="M 2944 2338 Q 2944 3213 2780 3570 Q 2616 3928 2228 3928 Q 1841 3928 1675 3570 Q 1509 3213 1509 2338 Q 1509 1453 1675 1090 Q 1841 728 2228 728 Q 2613 728 2778 1090 Q 2944 1453 2944 2338 z M 4147 2328 Q 4147 1169 3647 539 Q 3147 -91 2228 -91 Q 1306 -91 806 539 Q 306 1169 306 2328 Q 306 3491 806 4120 Q 1306 4750 2228 4750 Q 3147 4750 3647 4120 Q 4147 3491 4147 2328 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-5" onclick="(()=&gt;{console.log('text 5 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(383.164961 245.359486) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-6" onclick="(()=&gt;{console.log('text 6 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(197.191669 463.022351) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-35"
d="M 678 4666 L 3669 4666 L 3669 3781 L 1638 3781 L 1638 3059 Q 1775 3097 1914 3117 Q 2053 3138 2203 3138 Q 3056 3138 3531 2711 Q 4006 2284 4006 1522 Q 4006 766 3489 337 Q 2972 -91 2053 -91 Q 1656 -91 1267 -14 Q 878 63 494 219 L 494 1166 Q 875 947 1217 837 Q 1559 728 1863 728 Q 2300 728 2551 942 Q 2803 1156 2803 1522 Q 2803 1891 2551 2103 Q 2300 2316 1863 2316 Q 1603 2316 1309 2248 Q 1016 2181 678 2041 L 678 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-7" onclick="(()=&gt;{console.log('text 7 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(152.092875 295.851586) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-36"
d="M 2316 2303 Q 2000 2303 1842 2098 Q 1684 1894 1684 1484 Q 1684 1075 1842 870 Q 2000 666 2316 666 Q 2634 666 2792 870 Q 2950 1075 2950 1484 Q 2950 1894 2792 2098 Q 2634 2303 2316 2303 z M 3803 4544 L 3803 3681 Q 3506 3822 3243 3889 Q 2981 3956 2731 3956 Q 2194 3956 1894 3657 Q 1594 3359 1544 2772 Q 1750 2925 1990 3001 Q 2231 3078 2516 3078 Q 3231 3078 3670 2659 Q 4109 2241 4109 1563 Q 4109 813 3618 361 Q 3128 -91 2303 -91 Q 1394 -91 895 523 Q 397 1138 397 2266 Q 397 3422 980 4083 Q 1563 4744 2578 4744 Q 2900 4744 3203 4694 Q 3506 4644 3803 4544 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-8" onclick="(()=&gt;{console.log('text 8 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(415.720934 222.332954) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-9" onclick="(()=&gt;{console.log('text 9 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(337.329637 110.918523) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-10" onclick="(()=&gt;{console.log('text 10 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(316.995629 222.507171) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-39"
d="M 641 103 L 641 966 Q 928 831 1190 764 Q 1453 697 1709 697 Q 2247 697 2547 995 Q 2847 1294 2900 1881 Q 2688 1725 2447 1647 Q 2206 1569 1925 1569 Q 1209 1569 770 1986 Q 331 2403 331 3084 Q 331 3838 820 4291 Q 1309 4744 2131 4744 Q 3044 4744 3544 4128 Q 4044 3513 4044 2388 Q 4044 1231 3459 570 Q 2875 -91 1856 -91 Q 1528 -91 1228 -42 Q 928 6 641 103 z M 2125 2350 Q 2441 2350 2600 2554 Q 2759 2759 2759 3169 Q 2759 3575 2600 3781 Q 2441 3988 2125 3988 Q 1809 3988 1650 3781 Q 1491 3575 1491 3169 Q 1491 2759 1650 2554 Q 1809 2350 2125 2350 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-11" onclick="(()=&gt;{console.log('text 11 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(354.725926 205.013187) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-12" onclick="(()=&gt;{console.log('text 12 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(358.281208 151.471241) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-13" onclick="(()=&gt;{console.log('text 13 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(333.112734 173.726092) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-14" onclick="(()=&gt;{console.log('text 14 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(159.046959 329.273098) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-15" onclick="(()=&gt;{console.log('text 15 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(253.761934 185.199227) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-16" onclick="(()=&gt;{console.log('text 16 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(180.944121 266.046391) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-33"
d="M 2981 2516 Q 3453 2394 3698 2092 Q 3944 1791 3944 1325 Q 3944 631 3412 270 Q 2881 -91 1863 -91 Q 1503 -91 1142 -33 Q 781 25 428 141 L 428 1069 Q 766 900 1098 814 Q 1431 728 1753 728 Q 2231 728 2486 893 Q 2741 1059 2741 1369 Q 2741 1688 2480 1852 Q 2219 2016 1709 2016 L 1228 2016 L 1228 2791 L 1734 2791 Q 2188 2791 2409 2933 Q 2631 3075 2631 3366 Q 2631 3634 2415 3781 Q 2200 3928 1806 3928 Q 1516 3928 1219 3862 Q 922 3797 628 3669 L 628 4550 Q 984 4650 1334 4700 Q 1684 4750 2022 4750 Q 2931 4750 3382 4451 Q 3834 4153 3834 3553 Q 3834 3144 3618 2883 Q 3403 2622 2981 2516 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-17" onclick="(()=&gt;{console.log('text 17 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(269.606655 465.933789) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-18" onclick="(()=&gt;{console.log('text 18 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(325.583218 272.653614) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-19" onclick="(()=&gt;{console.log('text 19 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(408.411614 350.310389) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-20" onclick="(()=&gt;{console.log('text 20 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(326.31723 341.408828) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-21" onclick="(()=&gt;{console.log('text 21 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(195.638162 351.242444) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-22" onclick="(()=&gt;{console.log('text 22 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(279.71061 367.236222) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-23" onclick="(()=&gt;{console.log('text 23 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(267.849143 392.30993) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-24" onclick="(()=&gt;{console.log('text 24 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(284.880842 424.704655) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-25" onclick="(()=&gt;{console.log('text 25 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(415.504337 381.632604) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-26" onclick="(()=&gt;{console.log('text 26 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(196.864977 220.377413) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-27" onclick="(()=&gt;{console.log('text 27 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(234.462583 357.393433) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-28" onclick="(()=&gt;{console.log('text 28 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(145.697818 426.464523) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-29" onclick="(()=&gt;{console.log('text 29 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(290.510319 335.777161) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-30" onclick="(()=&gt;{console.log('text 30 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(269.445395 310.291491) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-31" onclick="(()=&gt;{console.log('text 31 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(251.655337 429.737528) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-32" onclick="(()=&gt;{console.log('text 32 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(293.568025 261.436163) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-33" onclick="(()=&gt;{console.log('text 33 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(246.547954 266.344409) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-34" onclick="(()=&gt;{console.log('text 34 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(312.918088 406.332457) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-35" onclick="(()=&gt;{console.log('text 35 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(337.400892 383.442961) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-36" onclick="(()=&gt;{console.log('text 36 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(395.88896 406.065981) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-37" onclick="(()=&gt;{console.log('text 37 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(347.38477 238.99916) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-38" onclick="(()=&gt;{console.log('text 38 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(185.584411 411.315605) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-39" onclick="(()=&gt;{console.log('text 39 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(289.181126 197.86249) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-40" onclick="(()=&gt;{console.log('text 40 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(311.726285 371.889731) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-41" onclick="(()=&gt;{console.log('text 41 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(220.340369 413.270465) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-42" onclick="(()=&gt;{console.log('text 42 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(342.876801 422.653917) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-43" onclick="(()=&gt;{console.log('text 43 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(363.771039 368.733221) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-44" onclick="(()=&gt;{console.log('text 44 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(221.953701 439.459389) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-45" onclick="(()=&gt;{console.log('text 45 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(253.157028 477.463977) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-46" onclick="(()=&gt;{console.log('text 46 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(409.211471 262.775596) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-47" onclick="(()=&gt;{console.log('text 47 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(392.258494 438.041697) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-48" onclick="(()=&gt;{console.log('text 48 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(273.912603 286.090784) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-49" onclick="(()=&gt;{console.log('text 49 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(213.773188 264.727971) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-50" onclick="(()=&gt;{console.log('text 50 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(301.181549 458.138582) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="pe3de578e26">
<rect x="124.405104" y="69.12" width="341.589792" height="443.52" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,898 @@
<?xml version='1.0' encoding='utf-8'?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:ns2="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="100 60 420 450" version="1.1">
<metadata>
<rdf:RDF>
<ns2:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2023-11-19T11:21:55.705408</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<ns2:Agent>
<dc:title>Matplotlib v3.7.1, https://matplotlib.org/</dc:title>
</ns2:Agent>
</dc:creator>
</ns2:Work>
</rdf:RDF>
</metadata>
<defs>
<style type="text/css">
* {
stroke-linejoin: round;
stroke-linecap: butt
}
</style>
<style type="text/css">
.beatrice-node-pointer {
cursor: pointer;
}
.beatrice-node-pointer:hover {
stroke: gray;
}
.beatrice-node-pointer-selected {
stroke: #ef6767c2;
stroke-width: 3
}
.beatrice-text-pointer {
cursor: pointer;
pointer-events: none
}
.beatrice-text-pointer:hover {
/* ホバー時のスタイルは既に設定されたスタイルと異なる特定の属性を変更することができます。 */
}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 576 L 576 576 L 576 0 L 0 0 z " style="fill: #ffffff" />
</g>
<g id="axes_1">
<g id="LineCollection_1">
<path d="M 383.475478 335.382791 L 350.123561 336.312105 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 383.475478 335.382791 L 393.562573 295.917472 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 383.475478 335.382791 L 396.396073 371.656412 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 395.592267 184.349842 L 344.302973 166.290216 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 166.614267 246.553188 L 214.405523 244.575019 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 362.886037 395.352171 L 389.299516 416.267064 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 362.886037 395.352171 L 367.134249 434.454954 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 362.886037 395.352171 L 396.396073 371.656412 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 362.886037 395.352171 L 321.091057 403.95329 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 291.699254 114.456198 L 287.429936 148.935339 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 309.72476 346.813492 L 326.464644 303.679747 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 396.396073 371.656412 L 422.276969 403.842356 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 396.396073 371.656412 L 419.504487 334.14189 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 311.713 188.802087 L 278.840744 190.572938 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 311.713 188.802087 L 287.429936 148.935339 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 311.713 188.802087 L 344.302973 166.290216 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 213.805036 285.720019 L 216.196468 317.113868 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 213.805036 285.720019 L 241.321249 255.242558 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 213.805036 285.720019 L 169.41455 268.66905 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 326.464644 303.679747 L 341.073251 272.287852 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 326.464644 303.679747 L 281.878613 277.846944 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 326.464644 303.679747 L 350.123561 336.312105 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 468.104517 290.196764 L 453.314054 329.209099 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 252.522958 107.607273 L 287.429936 148.935339 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 241.817 158.353487 L 278.840744 190.572938 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 278.840744 190.572938 L 264.569363 223.030096 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 190.32114 223.314542 L 214.405523 244.575019 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 233.41271 348.401671 L 216.196468 317.113868 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 122.295483 352.502553 L 162.445269 355.484449 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 158.624602 400.46174 L 162.445269 355.484449 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 214.405523 244.575019 L 241.321249 255.242558 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 214.405523 244.575019 L 207.563253 203.013335 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 264.569363 223.030096 L 298.808991 236.296491 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 264.569363 223.030096 L 241.321249 255.242558 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 264.569363 223.030096 L 236.649711 206.251683 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 220.79649 394.869471 L 213.931911 434.927829 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 220.79649 394.869471 L 208.453065 361.465389 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 241.321249 255.242558 L 281.878613 277.846944 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 257.120877 296.156882 L 281.878613 277.846944 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 453.314054 329.209099 L 419.504487 334.14189 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 287.180764 309.56402 L 281.878613 277.846944 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 287.429936 148.935339 L 321.071206 134.027026 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 300.632415 433.812968 L 321.091057 403.95329 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 216.196468 317.113868 L 181.944484 318.835753 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 216.196468 317.113868 L 208.453065 361.465389 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 419.504487 334.14189 L 436.061109 363.566053 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 366.514998 474.152727 L 367.134249 434.454954 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 208.453065 361.465389 L 162.445269 355.484449 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
</g>
<g id="PathCollection_1">
<defs>
<path id="C0_0_3858269516"
d="M 0 11.18034 C 2.965061 11.18034 5.80908 10.002309 7.905694 7.905694 C 10.002309 5.80908 11.18034 2.965061 11.18034 -0 C 11.18034 -2.965061 10.002309 -5.80908 7.905694 -7.905694 C 5.80908 -10.002309 2.965061 -11.18034 0 -11.18034 C -2.965061 -11.18034 -5.80908 -10.002309 -7.905694 -7.905694 C -10.002309 -5.80908 -11.18034 -2.965061 -11.18034 0 C -11.18034 2.965061 -10.002309 5.80908 -7.905694 7.905694 C -5.80908 10.002309 -2.965061 11.18034 0 11.18034 z " />
</defs>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-0" onclick="(()=&gt;{console.log('node 0')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="383.475478" y="335.382791" style="fill: #fde2bb" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-1" onclick="(()=&gt;{console.log('node 1')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="393.562573" y="295.917472" style="fill: #fdba68" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-2" onclick="(()=&gt;{console.log('node 2')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="395.592267" y="184.349842" style="fill: #fbe9cf" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-3" onclick="(()=&gt;{console.log('node 3')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="166.614267" y="246.553188" style="fill: #7e70ab" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-4" onclick="(()=&gt;{console.log('node 4')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="362.886037" y="395.352171" style="fill: #e8e9f1" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-5" onclick="(()=&gt;{console.log('node 5')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="291.699254" y="114.456198" style="fill: #f9b158" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-6" onclick="(()=&gt;{console.log('node 6')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="309.72476" y="346.813492" style="fill: #e4e5f0" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-7" onclick="(()=&gt;{console.log('node 7')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="396.396073" y="371.656412" style="fill: #fdcc8c" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-8" onclick="(()=&gt;{console.log('node 8')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="311.713" y="188.802087" style="fill: #fedeb3" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-9" onclick="(()=&gt;{console.log('node 9')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="213.805036" y="285.720019" style="fill: #bab5d7" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-10"
onclick="(()=&gt;{console.log('node 10')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="326.464644" y="303.679747" style="fill: #eaebf2" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-11"
onclick="(()=&gt;{console.log('node 11')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="468.104517" y="290.196764" style="fill: #f7f7f6" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-12"
onclick="(()=&gt;{console.log('node 12')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="169.41455" y="268.66905" style="fill: #dfe1ee" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-13"
onclick="(()=&gt;{console.log('node 13')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="252.522958" y="107.607273" style="fill: #eff0f4" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-14"
onclick="(()=&gt;{console.log('node 14')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="241.817" y="158.353487" style="fill: #e58a20" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-15"
onclick="(()=&gt;{console.log('node 15')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="278.840744" y="190.572938" style="fill: #fedbac" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-16"
onclick="(()=&gt;{console.log('node 16')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="190.32114" y="223.314542" style="fill: #dfe1ee" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-17"
onclick="(()=&gt;{console.log('node 17')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="233.41271" y="348.401671" style="fill: #c3c0dd" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-18"
onclick="(()=&gt;{console.log('node 18')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="122.295483" y="352.502553" style="fill: #fed8a6" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-19"
onclick="(()=&gt;{console.log('node 19')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="158.624602" y="400.46174" style="fill: #f7f6f3" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-20"
onclick="(()=&gt;{console.log('node 20')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="214.405523" y="244.575019" style="fill: #f9f2e9" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-21"
onclick="(()=&gt;{console.log('node 21')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="264.569363" y="223.030096" style="fill: #faecd7" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-22"
onclick="(()=&gt;{console.log('node 22')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="220.79649" y="394.869471" style="fill: #fbead2" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-23"
onclick="(()=&gt;{console.log('node 23')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="344.302973" y="166.290216" style="fill: #feddaf" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-24"
onclick="(()=&gt;{console.log('node 24')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="241.321249" y="255.242558" style="fill: #c3c0dd" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-25"
onclick="(()=&gt;{console.log('node 25')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="257.120877" y="296.156882" style="fill: #f9f0e4" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-26"
onclick="(()=&gt;{console.log('node 26')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="207.563253" y="203.013335" style="fill: #fbebd5" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-27"
onclick="(()=&gt;{console.log('node 27')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="453.314054" y="329.209099" style="fill: #fdc47b" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-28"
onclick="(()=&gt;{console.log('node 28')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="350.123561" y="336.312105" style="fill: #fbe9cf" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-29"
onclick="(()=&gt;{console.log('node 29')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="287.180764" y="309.56402" style="fill: #f7f6f3" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-30"
onclick="(()=&gt;{console.log('node 30')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="287.429936" y="148.935339" style="fill: #fbebd5" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-31"
onclick="(()=&gt;{console.log('node 31')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="281.878613" y="277.846944" style="fill: #d1d1e6" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-32"
onclick="(()=&gt;{console.log('node 32')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="300.632415" y="433.812968" style="fill: #fde2bb" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-33"
onclick="(()=&gt;{console.log('node 33')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="216.196468" y="317.113868" style="fill: #dddfed" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-34"
onclick="(()=&gt;{console.log('node 34')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="419.504487" y="334.14189" style="fill: #fdc57f" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-35"
onclick="(()=&gt;{console.log('node 35')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="321.071206" y="134.027026" style="fill: #fee0b6" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-36"
onclick="(()=&gt;{console.log('node 36')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="366.514998" y="474.152727" style="fill: #fdbd6e" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-37"
onclick="(()=&gt;{console.log('node 37')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="208.453065" y="361.465389" style="fill: #cccbe3" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-38"
onclick="(()=&gt;{console.log('node 38')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="236.649711" y="206.251683" style="fill: #faecd7" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-39"
onclick="(()=&gt;{console.log('node 39')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="298.808991" y="236.296491" style="fill: #fdc57f" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-40"
onclick="(()=&gt;{console.log('node 40')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="181.944484" y="318.835753" style="fill: #f9f0e4" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-41"
onclick="(()=&gt;{console.log('node 41')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="367.134249" y="434.454954" style="fill: #f6f6f7" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-42"
onclick="(()=&gt;{console.log('node 42')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="422.276969" y="403.842356" style="fill: #fdbf72" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-43"
onclick="(()=&gt;{console.log('node 43')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="321.091057" y="403.95329" style="fill: #f8f5f1" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-44"
onclick="(()=&gt;{console.log('node 44')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="162.445269" y="355.484449" style="fill: #eaebf2" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-45"
onclick="(()=&gt;{console.log('node 45')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="341.073251" y="272.287852" style="fill: #f6aa4f" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-46"
onclick="(()=&gt;{console.log('node 46')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="389.299516" y="416.267064" style="fill: #de8013" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-47"
onclick="(()=&gt;{console.log('node 47')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="213.931911" y="434.927829" style="fill: #fbb55e" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-48"
onclick="(()=&gt;{console.log('node 48')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="436.061109" y="363.566053" style="fill: #ebecf3" />
</g>
</g>
<g id="beatrice-text-male-0" onclick="(()=&gt;{console.log('text 0 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(379.30079 338.694041) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-31"
d="M 750 831 L 1813 831 L 1813 3847 L 722 3622 L 722 4441 L 1806 4666 L 2950 4666 L 2950 831 L 4013 831 L 4013 0 L 750 0 L 750 831 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
</g>
</g>
</g>
<g id="beatrice-text-male-1" onclick="(()=&gt;{console.log('text 1 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(389.387885 299.228722) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-33"
d="M 2981 2516 Q 3453 2394 3698 2092 Q 3944 1791 3944 1325 Q 3944 631 3412 270 Q 2881 -91 1863 -91 Q 1503 -91 1142 -33 Q 781 25 428 141 L 428 1069 Q 766 900 1098 814 Q 1431 728 1753 728 Q 2231 728 2486 893 Q 2741 1059 2741 1369 Q 2741 1688 2480 1852 Q 2219 2016 1709 2016 L 1228 2016 L 1228 2791 L 1734 2791 Q 2188 2791 2409 2933 Q 2631 3075 2631 3366 Q 2631 3634 2415 3781 Q 2200 3928 1806 3928 Q 1516 3928 1219 3862 Q 922 3797 628 3669 L 628 4550 Q 984 4650 1334 4700 Q 1684 4750 2022 4750 Q 2931 4750 3382 4451 Q 3834 4153 3834 3553 Q 3834 3144 3618 2883 Q 3403 2622 2981 2516 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-33" />
</g>
</g>
</g>
<g id="beatrice-text-male-2" onclick="(()=&gt;{console.log('text 2 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(391.41758 187.661092) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-35"
d="M 678 4666 L 3669 4666 L 3669 3781 L 1638 3781 L 1638 3059 Q 1775 3097 1914 3117 Q 2053 3138 2203 3138 Q 3056 3138 3531 2711 Q 4006 2284 4006 1522 Q 4006 766 3489 337 Q 2972 -91 2053 -91 Q 1656 -91 1267 -14 Q 878 63 494 219 L 494 1166 Q 875 947 1217 837 Q 1559 728 1863 728 Q 2300 728 2551 942 Q 2803 1156 2803 1522 Q 2803 1891 2551 2103 Q 2300 2316 1863 2316 Q 1603 2316 1309 2248 Q 1016 2181 678 2041 L 678 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-35" />
</g>
</g>
</g>
<g id="beatrice-text-male-3" onclick="(()=&gt;{console.log('text 3 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(162.43958 249.864438) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-36"
d="M 2316 2303 Q 2000 2303 1842 2098 Q 1684 1894 1684 1484 Q 1684 1075 1842 870 Q 2000 666 2316 666 Q 2634 666 2792 870 Q 2950 1075 2950 1484 Q 2950 1894 2792 2098 Q 2634 2303 2316 2303 z M 3803 4544 L 3803 3681 Q 3506 3822 3243 3889 Q 2981 3956 2731 3956 Q 2194 3956 1894 3657 Q 1594 3359 1544 2772 Q 1750 2925 1990 3001 Q 2231 3078 2516 3078 Q 3231 3078 3670 2659 Q 4109 2241 4109 1563 Q 4109 813 3618 361 Q 3128 -91 2303 -91 Q 1394 -91 895 523 Q 397 1138 397 2266 Q 397 3422 980 4083 Q 1563 4744 2578 4744 Q 2900 4744 3203 4694 Q 3506 4644 3803 4544 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-36" />
</g>
</g>
</g>
<g id="beatrice-text-male-4" onclick="(()=&gt;{console.log('text 4 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(358.71135 398.663421) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-39"
d="M 641 103 L 641 966 Q 928 831 1190 764 Q 1453 697 1709 697 Q 2247 697 2547 995 Q 2847 1294 2900 1881 Q 2688 1725 2447 1647 Q 2206 1569 1925 1569 Q 1209 1569 770 1986 Q 331 2403 331 3084 Q 331 3838 820 4291 Q 1309 4744 2131 4744 Q 3044 4744 3544 4128 Q 4044 3513 4044 2388 Q 4044 1231 3459 570 Q 2875 -91 1856 -91 Q 1528 -91 1228 -42 Q 928 6 641 103 z M 2125 2350 Q 2441 2350 2600 2554 Q 2759 2759 2759 3169 Q 2759 3575 2600 3781 Q 2441 3988 2125 3988 Q 1809 3988 1650 3781 Q 1491 3575 1491 3169 Q 1491 2759 1650 2554 Q 1809 2350 2125 2350 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-39" />
</g>
</g>
</g>
<g id="beatrice-text-male-5" onclick="(()=&gt;{console.log('text 5 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(283.349879 117.767448) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-6" onclick="(()=&gt;{console.log('text 6 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(301.375385 350.124742) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-32"
d="M 1844 884 L 3897 884 L 3897 0 L 506 0 L 506 884 L 2209 2388 Q 2438 2594 2547 2791 Q 2656 2988 2656 3200 Q 2656 3528 2436 3728 Q 2216 3928 1850 3928 Q 1569 3928 1234 3808 Q 900 3688 519 3450 L 519 4475 Q 925 4609 1322 4679 Q 1719 4750 2100 4750 Q 2938 4750 3402 4381 Q 3866 4013 3866 3353 Q 3866 2972 3669 2642 Q 3472 2313 2841 1759 L 1844 884 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-7" onclick="(()=&gt;{console.log('text 7 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(388.046698 374.967662) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-8" onclick="(()=&gt;{console.log('text 8 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(303.363625 192.113337) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-30"
d="M 2944 2338 Q 2944 3213 2780 3570 Q 2616 3928 2228 3928 Q 1841 3928 1675 3570 Q 1509 3213 1509 2338 Q 1509 1453 1675 1090 Q 1841 728 2228 728 Q 2613 728 2778 1090 Q 2944 1453 2944 2338 z M 4147 2328 Q 4147 1169 3647 539 Q 3147 -91 2228 -91 Q 1306 -91 806 539 Q 306 1169 306 2328 Q 306 3491 806 4120 Q 1306 4750 2228 4750 Q 3147 4750 3647 4120 Q 4147 3491 4147 2328 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-9" onclick="(()=&gt;{console.log('text 9 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(205.455661 289.031269) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-10" onclick="(()=&gt;{console.log('text 10 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(318.115269 306.990997) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-11" onclick="(()=&gt;{console.log('text 11 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(459.755142 293.508014) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-12" onclick="(()=&gt;{console.log('text 12 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(161.065175 271.9803) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-38"
d="M 2228 2088 Q 1891 2088 1709 1903 Q 1528 1719 1528 1375 Q 1528 1031 1709 848 Q 1891 666 2228 666 Q 2563 666 2741 848 Q 2919 1031 2919 1375 Q 2919 1722 2741 1905 Q 2563 2088 2228 2088 z M 1350 2484 Q 925 2613 709 2878 Q 494 3144 494 3541 Q 494 4131 934 4440 Q 1375 4750 2228 4750 Q 3075 4750 3515 4442 Q 3956 4134 3956 3541 Q 3956 3144 3739 2878 Q 3522 2613 3097 2484 Q 3572 2353 3814 2058 Q 4056 1763 4056 1313 Q 4056 619 3595 264 Q 3134 -91 2228 -91 Q 1319 -91 855 264 Q 391 619 391 1313 Q 391 1763 633 2058 Q 875 2353 1350 2484 z M 1631 3419 Q 1631 3141 1786 2991 Q 1941 2841 2228 2841 Q 2509 2841 2662 2991 Q 2816 3141 2816 3419 Q 2816 3697 2662 3845 Q 2509 3994 2228 3994 Q 1941 3994 1786 3844 Q 1631 3694 1631 3419 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-13" onclick="(()=&gt;{console.log('text 13 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(244.173583 110.918523) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-14" onclick="(()=&gt;{console.log('text 14 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(233.467625 161.664737) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-15" onclick="(()=&gt;{console.log('text 15 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(270.491369 193.884188) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-16" onclick="(()=&gt;{console.log('text 16 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(181.971765 226.625792) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-34"
d="M 2356 3675 L 1038 1722 L 2356 1722 L 2356 3675 z M 2156 4666 L 3494 4666 L 3494 1722 L 4159 1722 L 4159 850 L 3494 850 L 3494 0 L 2356 0 L 2356 850 L 288 850 L 288 1881 L 2156 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-17" onclick="(()=&gt;{console.log('text 17 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(225.063335 351.712921) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-37"
d="M 428 4666 L 3944 4666 L 3944 3988 L 2125 0 L 953 0 L 2675 3781 L 428 3781 L 428 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-18" onclick="(()=&gt;{console.log('text 18 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(113.946108 355.813803) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-19" onclick="(()=&gt;{console.log('text 19 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(150.275227 403.77299) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-20" onclick="(()=&gt;{console.log('text 20 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(206.056148 247.886269) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-21" onclick="(()=&gt;{console.log('text 21 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(256.219988 226.341346) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-22" onclick="(()=&gt;{console.log('text 22 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(212.447115 398.180721) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-23" onclick="(()=&gt;{console.log('text 23 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(335.953598 169.601466) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-24" onclick="(()=&gt;{console.log('text 24 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(232.971874 258.553808) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-25" onclick="(()=&gt;{console.log('text 25 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(248.771502 299.468132) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-26" onclick="(()=&gt;{console.log('text 26 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(199.213878 206.324585) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-27" onclick="(()=&gt;{console.log('text 27 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(444.964679 332.520349) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-28" onclick="(()=&gt;{console.log('text 28 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(341.774186 339.623355) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-29" onclick="(()=&gt;{console.log('text 29 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(278.831389 312.87527) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-30" onclick="(()=&gt;{console.log('text 30 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(279.080561 152.246589) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-31" onclick="(()=&gt;{console.log('text 31 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(273.529238 281.158194) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-32" onclick="(()=&gt;{console.log('text 32 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(292.28304 437.124218) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-33" onclick="(()=&gt;{console.log('text 33 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(207.847093 320.425118) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-34" onclick="(()=&gt;{console.log('text 34 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(411.155112 337.45314) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-35" onclick="(()=&gt;{console.log('text 35 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(312.721831 137.338276) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-36" onclick="(()=&gt;{console.log('text 36 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(358.165623 477.463977) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-37" onclick="(()=&gt;{console.log('text 37 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(200.10369 364.776639) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-38" onclick="(()=&gt;{console.log('text 38 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(228.300336 209.562933) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-39" onclick="(()=&gt;{console.log('text 39 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(290.459616 239.607741) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-40" onclick="(()=&gt;{console.log('text 40 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(173.595109 322.147003) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-41" onclick="(()=&gt;{console.log('text 41 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(358.784874 437.766204) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-42" onclick="(()=&gt;{console.log('text 42 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(413.927594 407.153606) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-43" onclick="(()=&gt;{console.log('text 43 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(312.741682 407.26454) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-44" onclick="(()=&gt;{console.log('text 44 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(154.095894 358.795699) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-45" onclick="(()=&gt;{console.log('text 45 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(332.723876 275.599102) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-46" onclick="(()=&gt;{console.log('text 46 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(380.950141 419.578314) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-47" onclick="(()=&gt;{console.log('text 47 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(205.582536 438.239079) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-48" onclick="(()=&gt;{console.log('text 48 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(423.537047 366.877303) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
<use xlink:href="#DejaVuSans-Bold-30" x="139.160156" />
</g>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="pd42c8a995e">
<rect x="85.985534" y="69.12" width="418.428931" height="443.52" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -21,8 +21,8 @@
{
"name": "configArea",
"options": {
"detectors": ["dio", "harvest", "crepe", "crepe_full", "crepe_tiny"],
"inputChunkNums": [8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048]
"detectors": ["dio", "harvest", "crepe", "crepe_full", "crepe_tiny", "rmvpe", "rmvpe_onnx", "fcpe"],
"inputChunkNums": [1, 2, 4, 6, 8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048, 4096, 8192, 16384]
}
}
]

View File

@ -0,0 +1 @@
onnxdirectML-cuda

View File

@ -0,0 +1 @@
web

View File

@ -1,10 +1 @@
<!DOCTYPE html>
<html style="width: 100%; height: 100%; overflow: hidden">
<head>
<meta charset="utf-8" />
<title>Voice Changer Client Demo</title>
<script defer src="index.js"></script></head>
<body style="width: 100%; height: 100%; margin: 0px">
<div id="app" style="width: 100%; height: 100%"></div>
</body>
</html>
<!doctype html><html style="width:100%;height:100%;overflow:hidden"><head><meta charset="utf-8"/><title>Voice Changer Client Demo</title><script defer="defer" src="index.js"></script></head><body style="width:100%;height:100%;margin:0"><div id="app" style="width:100%;height:100%"></div></body></html>

File diff suppressed because one or more lines are too long

35
client/demo/dist/index.js.LICENSE.txt vendored Normal file
View File

@ -0,0 +1,35 @@
/*! regenerator-runtime -- Copyright (c) 2014-present, Facebook, Inc. -- license (MIT): https://github.com/facebook/regenerator/blob/main/LICENSE */
/*!**********************!*\
!*** ./src/index.ts ***!
\**********************/
/**
* @license React
* react-dom.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* react.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
/**
* @license React
* scheduler.production.min.js
*
* Copyright (c) Facebook, Inc. and its affiliates.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

File diff suppressed because it is too large Load Diff

View File

@ -11,9 +11,16 @@
"build:dev": "npm-run-all clean webpack:dev",
"start": "webpack-dev-server --config webpack.dev.js",
"build:mod": "cd ../lib && npm run build:dev && cd - && cp -r ../lib/dist/* node_modules/@dannadori/voice-changer-client-js/dist/",
"build:mod_dos": "cd ../lib && npm run build:dev && cd ../demo && copy ../lib/dist/index.js node_modules/@dannadori/voice-changer-client-js/dist/",
"build:mod_dos2": "copy ../lib/dist/index.js node_modules/@dannadori/voice-changer-client-js/dist/",
"test": "echo \"Error: no test specified\" && exit 1"
"build:mod_dos": "cd ../lib && npm run build:dev && cd ../demo && npm-run-all build:mod_copy",
"build:mod_copy": "XCOPY ..\\lib\\dist\\* .\\node_modules\\@dannadori\\voice-changer-client-js\\dist\\* /s /e /h /y",
"test": "echo \"Error: no test specified\" && exit 1",
"____ comment ____": "ウェブバージョンのスクリプト",
"clean:web": "rimraf dist_web/",
"webpack:web:prod": "npx webpack --config webpack_web.prod.js && copy .\\public\\info_web .\\dist_web\\info && copy .\\public\\assets\\gui_settings\\edition_web.txt .\\dist_web\\assets\\gui_settings\\edition.txt",
"webpack:web:dev": "npx webpack --config webpack_web.dev.js && copy .\\public\\info_web .\\dist_web\\info && copy .\\public\\assets\\gui_settings\\edition_web.txt .\\dist_web\\assets\\gui_settings\\edition.txt",
"build:web:prod": "npm-run-all clean:web webpack:web:prod",
"build:web:dev": "npm-run-all clean:web webpack:web:dev",
"start:web": "webpack-dev-server --config webpack_web.dev.js"
},
"keywords": [
"voice conversion"
@ -21,45 +28,51 @@
"author": "wataru.okada@flect.co.jp",
"license": "ISC",
"devDependencies": {
"@babel/core": "^7.22.6",
"@babel/plugin-transform-runtime": "^7.22.6",
"@babel/preset-env": "^7.22.6",
"@babel/preset-react": "^7.22.5",
"@babel/preset-typescript": "^7.22.5",
"@types/node": "^20.3.3",
"@types/react": "^18.2.14",
"@types/react-dom": "^18.2.6",
"autoprefixer": "^10.4.14",
"babel-loader": "^9.1.2",
"copy-webpack-plugin": "^11.0.0",
"css-loader": "^6.8.1",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"@babel/core": "^7.24.0",
"@babel/plugin-transform-runtime": "^7.24.0",
"@babel/preset-env": "^7.24.0",
"@babel/preset-react": "^7.23.3",
"@babel/preset-typescript": "^7.23.3",
"@types/node": "^20.11.21",
"@types/react": "^18.2.60",
"@types/react-dom": "^18.2.19",
"autoprefixer": "^10.4.17",
"babel-loader": "^9.1.3",
"copy-webpack-plugin": "^12.0.2",
"css-loader": "^6.10.0",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.33.2",
"eslint-webpack-plugin": "^4.0.1",
"html-loader": "^4.2.0",
"html-webpack-plugin": "^5.5.3",
"html-loader": "^5.0.0",
"html-webpack-plugin": "^5.6.0",
"npm-run-all": "^4.1.5",
"postcss-loader": "^7.3.3",
"postcss-loader": "^8.1.1",
"postcss-nested": "^6.0.1",
"prettier": "^2.8.8",
"rimraf": "^5.0.1",
"style-loader": "^3.3.3",
"ts-loader": "^9.4.4",
"prettier": "^3.2.5",
"rimraf": "^5.0.5",
"style-loader": "^3.3.4",
"ts-loader": "^9.5.1",
"tsconfig-paths": "^4.2.0",
"typescript": "^5.1.6",
"webpack": "^5.88.1",
"typescript": "^5.3.3",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
"webpack-dev-server": "^5.0.2"
},
"dependencies": {
"@dannadori/voice-changer-client-js": "^1.0.159",
"@fortawesome/fontawesome-svg-core": "^6.4.0",
"@fortawesome/free-brands-svg-icons": "^6.4.0",
"@fortawesome/free-regular-svg-icons": "^6.4.0",
"@fortawesome/free-solid-svg-icons": "^6.4.0",
"@alexanderolsen/libsamplerate-js": "^2.1.1",
"@dannadori/voice-changer-client-js": "^1.0.182",
"@dannadori/voice-changer-js": "^1.0.2",
"@dannadori/worker-manager": "^1.0.20",
"@fortawesome/fontawesome-svg-core": "^6.5.1",
"@fortawesome/free-brands-svg-icons": "^6.5.1",
"@fortawesome/free-regular-svg-icons": "^6.5.1",
"@fortawesome/free-solid-svg-icons": "^6.5.1",
"@fortawesome/react-fontawesome": "^0.2.0",
"@tensorflow/tfjs": "^4.17.0",
"onnxruntime-web": "^1.17.1",
"protobufjs": "^7.2.6",
"react": "^18.2.0",
"react-dom": "^18.2.0"
}

View File

@ -0,0 +1,928 @@
<?xml version='1.0' encoding='utf-8'?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:ns2="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="100 60 420 450" version="1.1">
<metadata>
<rdf:RDF>
<ns2:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2023-11-19T11:21:56.358384</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<ns2:Agent>
<dc:title>Matplotlib v3.7.1, https://matplotlib.org/</dc:title>
</ns2:Agent>
</dc:creator>
</ns2:Work>
</rdf:RDF>
</metadata>
<defs>
<style type="text/css">
* {
stroke-linejoin: round;
stroke-linecap: butt
}
</style>
<style type="text/css">
.beatrice-node-pointer {
cursor: pointer;
}
.beatrice-node-pointer:hover {
stroke: gray;
}
.beatrice-node-pointer-selected {
stroke: #ef6767c2;
stroke-width: 3
}
.beatrice-text-pointer {
cursor: pointer;
pointer-events: none
}
.beatrice-text-pointer:hover {
/* ホバー時のスタイルは既に設定されたスタイルと異なる特定の属性を変更することができます。 */
}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 576 L 576 576 L 576 0 L 0 0 z " style="fill: #ffffff" />
</g>
<g id="axes_1">
<g id="LineCollection_1">
<path d="M 403.96157 149.258085 L 366.630583 148.159991 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 396.547407 371.476481 L 372.120414 365.421971 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 396.547407 371.476481 L 416.760989 346.999139 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 396.547407 371.476481 L 404.238335 402.754731 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 258.035169 326.134244 L 298.859694 332.465911 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 167.453327 366.897955 L 203.987537 347.931194 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 436.352807 416.173738 L 404.238335 402.754731 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 391.514336 242.048236 L 417.560846 259.464346 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 391.514336 242.048236 L 355.734145 235.68791 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 391.514336 242.048236 L 424.070309 219.021704 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 205.541044 459.711101 L 230.303076 436.148139 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 160.44225 292.540336 L 167.396334 325.961848 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 345.679012 107.607273 L 366.630583 148.159991 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 325.345004 219.195921 L 355.734145 235.68791 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 325.345004 219.195921 L 297.530501 194.55124 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 363.075301 201.701937 L 355.734145 235.68791 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 363.075301 201.701937 L 341.462109 170.414842 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 366.630583 148.159991 L 341.462109 170.414842 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 167.396334 325.961848 L 203.987537 347.931194 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 262.111309 181.887977 L 297.530501 194.55124 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 189.293496 262.735141 L 222.122563 261.416721 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 277.95603 462.622539 L 293.230217 421.393405 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 333.932593 269.342364 L 301.9174 258.124913 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 333.932593 269.342364 L 355.734145 235.68791 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 334.666605 338.097578 L 320.07566 368.578481 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 203.987537 347.931194 L 242.811958 354.082183 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 288.059985 363.924972 L 320.07566 368.578481 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 288.059985 363.924972 L 276.198518 388.99868 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 288.059985 363.924972 L 298.859694 332.465911 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 288.059985 363.924972 L 242.811958 354.082183 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 276.198518 388.99868 L 293.230217 421.393405 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 293.230217 421.393405 L 309.530924 454.827332 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 293.230217 421.393405 L 260.004712 426.426278 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 423.853712 378.321354 L 404.238335 402.754731 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 205.214352 217.066163 L 222.122563 261.416721 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 154.047193 423.153273 L 193.933786 408.004355 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 298.859694 332.465911 L 277.79477 306.980241 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 277.79477 306.980241 L 282.261978 282.779534 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 260.004712 426.426278 L 230.303076 436.148139 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 260.004712 426.426278 L 228.689744 409.959215 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 260.004712 426.426278 L 261.506403 474.152727 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 301.9174 258.124913 L 282.261978 282.779534 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 254.897329 263.033159 L 282.261978 282.779534 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 254.897329 263.033159 L 222.122563 261.416721 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 321.267463 403.021207 L 320.07566 368.578481 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 345.750267 380.131711 L 320.07566 368.578481 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 345.750267 380.131711 L 372.120414 365.421971 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 345.750267 380.131711 L 351.226176 419.342667 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 404.238335 402.754731 L 400.607869 434.730447 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
<path d="M 193.933786 408.004355 L 230.303076 436.148139 " clip-path="url(#pe3de578e26)"
style="fill: none; stroke: #808080" />
</g>
<g id="PathCollection_1">
<defs>
<path id="C0_0_b0ffb3bf4a"
d="M 0 11.18034 C 2.965061 11.18034 5.80908 10.002309 7.905694 7.905694 C 10.002309 5.80908 11.18034 2.965061 11.18034 -0 C 11.18034 -2.965061 10.002309 -5.80908 7.905694 -7.905694 C 5.80908 -10.002309 2.965061 -11.18034 0 -11.18034 C -2.965061 -11.18034 -5.80908 -10.002309 -7.905694 -7.905694 C -10.002309 -5.80908 -11.18034 -2.965061 -11.18034 0 C -11.18034 2.965061 -10.002309 5.80908 -7.905694 7.905694 C -5.80908 10.002309 -2.965061 11.18034 0 11.18034 z " />
</defs>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-0"
onclick="(()=&gt;{console.log('node 0')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="403.96157" y="149.258085" style="fill: #e7f5d2" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-1"
onclick="(()=&gt;{console.log('node 1')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="396.547407" y="371.476481" style="fill: #fbe8f2" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-2"
onclick="(()=&gt;{console.log('node 2')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="258.035169" y="326.134244" style="fill: #cfebaa" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-3"
onclick="(()=&gt;{console.log('node 3')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="167.453327" y="366.897955" style="fill: #f1f6e8" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-4"
onclick="(()=&gt;{console.log('node 4')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="436.352807" y="416.173738" style="fill: #e89ac6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-5"
onclick="(()=&gt;{console.log('node 5')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="391.514336" y="242.048236" style="fill: #f3bcdd" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-6"
onclick="(()=&gt;{console.log('node 6')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="205.541044" y="459.711101" style="fill: #fbd9ec" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-7"
onclick="(()=&gt;{console.log('node 7')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="160.44225" y="292.540336" style="fill: #9ed067" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-8"
onclick="(()=&gt;{console.log('node 8')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="424.070309" y="219.021704" style="fill: #e1f3c7" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-9"
onclick="(()=&gt;{console.log('node 9')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="345.679012" y="107.607273" style="fill: #d0ecad" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-10"
onclick="(()=&gt;{console.log('node 10')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="325.345004" y="219.195921" style="fill: #eff6e4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-11"
onclick="(()=&gt;{console.log('node 11')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="363.075301" y="201.701937" style="fill: #f9f0f5" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-12"
onclick="(()=&gt;{console.log('node 12')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="366.630583" y="148.159991" style="fill: #ebf6dc" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-13"
onclick="(()=&gt;{console.log('node 13')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="341.462109" y="170.414842" style="fill: #fad6ea" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-14"
onclick="(()=&gt;{console.log('node 14')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="167.396334" y="325.961848" style="fill: #f5f7f3" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-15"
onclick="(()=&gt;{console.log('node 15')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="262.111309" y="181.887977" style="fill: #e9f5d6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-16"
onclick="(()=&gt;{console.log('node 16')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="189.293496" y="262.735141" style="fill: #fce5f1" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-17"
onclick="(()=&gt;{console.log('node 17')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="277.95603" y="462.622539" style="fill: #c4e699" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-18"
onclick="(()=&gt;{console.log('node 18')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="333.932593" y="269.342364" style="fill: #f8f4f6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-19"
onclick="(()=&gt;{console.log('node 19')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="416.760989" y="346.999139" style="fill: #eef6e2" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-20"
onclick="(()=&gt;{console.log('node 20')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="334.666605" y="338.097578" style="fill: #f5f7f3" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-21"
onclick="(()=&gt;{console.log('node 21')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="203.987537" y="347.931194" style="fill: #edf6df" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-22"
onclick="(()=&gt;{console.log('node 22')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="288.059985" y="363.924972" style="fill: #ddf1c1" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-23"
onclick="(()=&gt;{console.log('node 23')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="276.198518" y="388.99868" style="fill: #f5f7f3" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-24"
onclick="(()=&gt;{console.log('node 24')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="293.230217" y="421.393405" style="fill: #f3f7ef" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-25"
onclick="(()=&gt;{console.log('node 25')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="423.853712" y="378.321354" style="fill: #edf6df" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-26"
onclick="(()=&gt;{console.log('node 26')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="205.214352" y="217.066163" style="fill: #e7f5d2" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-27"
onclick="(()=&gt;{console.log('node 27')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="242.811958" y="354.082183" style="fill: #d2ecb0" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-28"
onclick="(()=&gt;{console.log('node 28')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="154.047193" y="423.153273" style="fill: #e6f5d0" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-29"
onclick="(()=&gt;{console.log('node 29')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="298.859694" y="332.465911" style="fill: #ecf6de" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-30"
onclick="(()=&gt;{console.log('node 30')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="277.79477" y="306.980241" style="fill: #eaf5d9" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-31"
onclick="(()=&gt;{console.log('node 31')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="260.004712" y="426.426278" style="fill: #f9f1f5" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-32"
onclick="(()=&gt;{console.log('node 32')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="301.9174" y="258.124913" style="fill: #dbf0bf" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-33"
onclick="(()=&gt;{console.log('node 33')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="254.897329" y="263.033159" style="fill: #eff6e4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-34"
onclick="(()=&gt;{console.log('node 34')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="321.267463" y="403.021207" style="fill: #d0ecad" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-35"
onclick="(()=&gt;{console.log('node 35')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="345.750267" y="380.131711" style="fill: #f9eef4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-36"
onclick="(()=&gt;{console.log('node 36')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="404.238335" y="402.754731" style="fill: #f9eef4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-37"
onclick="(()=&gt;{console.log('node 37')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="355.734145" y="235.68791" style="fill: #f9eef4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-38"
onclick="(()=&gt;{console.log('node 38')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="193.933786" y="408.004355" style="fill: #f0f6e7" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-39"
onclick="(()=&gt;{console.log('node 39')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="297.530501" y="194.55124" style="fill: #f3f6ed" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-40"
onclick="(()=&gt;{console.log('node 40')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="320.07566" y="368.578481" style="fill: #dbf0bf" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-41"
onclick="(()=&gt;{console.log('node 41')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="228.689744" y="409.959215" style="fill: #f9eff4" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-42"
onclick="(()=&gt;{console.log('node 42')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="351.226176" y="419.342667" style="fill: #cfebaa" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-43"
onclick="(()=&gt;{console.log('node 43')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="372.120414" y="365.421971" style="fill: #f7f6f7" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-44"
onclick="(()=&gt;{console.log('node 44')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="230.303076" y="436.148139" style="fill: #f8cee6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-45"
onclick="(()=&gt;{console.log('node 45')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="261.506403" y="474.152727" style="fill: #e6f5d0" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-46"
onclick="(()=&gt;{console.log('node 46')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="417.560846" y="259.464346" style="fill: #b7e085" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-47"
onclick="(()=&gt;{console.log('node 47')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="400.607869" y="434.730447" style="fill: #f8cee6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-48"
onclick="(()=&gt;{console.log('node 48')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="282.261978" y="282.779534" style="fill: #d6eeb6" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-49"
onclick="(()=&gt;{console.log('node 49')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="222.122563" y="261.416721" style="fill: #edf6df" />
</g>
<g clip-path="url(#pe3de578e26)" id="beatrice-node-female-50"
onclick="(()=&gt;{console.log('node 50')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_b0ffb3bf4a" x="309.530924" y="454.827332" style="fill: #f9eef4" />
</g>
</g>
<g id="beatrice-text-female-0" onclick="(()=&gt;{console.log('text 0 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(399.786883 152.569335) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-32"
d="M 1844 884 L 3897 884 L 3897 0 L 506 0 L 506 884 L 2209 2388 Q 2438 2594 2547 2791 Q 2656 2988 2656 3200 Q 2656 3528 2436 3728 Q 2216 3928 1850 3928 Q 1569 3928 1234 3808 Q 900 3688 519 3450 L 519 4475 Q 925 4609 1322 4679 Q 1719 4750 2100 4750 Q 2938 4750 3402 4381 Q 3866 4013 3866 3353 Q 3866 2972 3669 2642 Q 3472 2313 2841 1759 L 1844 884 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-32" />
</g>
</g>
</g>
<g id="beatrice-text-female-1" onclick="(()=&gt;{console.log('text 1 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(392.37272 374.787731) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-34"
d="M 2356 3675 L 1038 1722 L 2356 1722 L 2356 3675 z M 2156 4666 L 3494 4666 L 3494 1722 L 4159 1722 L 4159 850 L 3494 850 L 3494 0 L 2356 0 L 2356 850 L 288 850 L 288 1881 L 2156 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-34" />
</g>
</g>
</g>
<g id="beatrice-text-female-2" onclick="(()=&gt;{console.log('text 2 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(253.860482 329.445494) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-37"
d="M 428 4666 L 3944 4666 L 3944 3988 L 2125 0 L 953 0 L 2675 3781 L 428 3781 L 428 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-37" />
</g>
</g>
</g>
<g id="beatrice-text-female-3" onclick="(()=&gt;{console.log('text 3 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(163.27864 370.209205) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-38"
d="M 2228 2088 Q 1891 2088 1709 1903 Q 1528 1719 1528 1375 Q 1528 1031 1709 848 Q 1891 666 2228 666 Q 2563 666 2741 848 Q 2919 1031 2919 1375 Q 2919 1722 2741 1905 Q 2563 2088 2228 2088 z M 1350 2484 Q 925 2613 709 2878 Q 494 3144 494 3541 Q 494 4131 934 4440 Q 1375 4750 2228 4750 Q 3075 4750 3515 4442 Q 3956 4134 3956 3541 Q 3956 3144 3739 2878 Q 3522 2613 3097 2484 Q 3572 2353 3814 2058 Q 4056 1763 4056 1313 Q 4056 619 3595 264 Q 3134 -91 2228 -91 Q 1319 -91 855 264 Q 391 619 391 1313 Q 391 1763 633 2058 Q 875 2353 1350 2484 z M 1631 3419 Q 1631 3141 1786 2991 Q 1941 2841 2228 2841 Q 2509 2841 2662 2991 Q 2816 3141 2816 3419 Q 2816 3697 2662 3845 Q 2509 3994 2228 3994 Q 1941 3994 1786 3844 Q 1631 3694 1631 3419 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-38" />
</g>
</g>
</g>
<g id="beatrice-text-female-4" onclick="(()=&gt;{console.log('text 4 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(428.003432 419.484988) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-31"
d="M 750 831 L 1813 831 L 1813 3847 L 722 3622 L 722 4441 L 1806 4666 L 2950 4666 L 2950 831 L 4013 831 L 4013 0 L 750 0 L 750 831 z "
transform="scale(0.015625)" />
<path id="DejaVuSans-Bold-30"
d="M 2944 2338 Q 2944 3213 2780 3570 Q 2616 3928 2228 3928 Q 1841 3928 1675 3570 Q 1509 3213 1509 2338 Q 1509 1453 1675 1090 Q 1841 728 2228 728 Q 2613 728 2778 1090 Q 2944 1453 2944 2338 z M 4147 2328 Q 4147 1169 3647 539 Q 3147 -91 2228 -91 Q 1306 -91 806 539 Q 306 1169 306 2328 Q 306 3491 806 4120 Q 1306 4750 2228 4750 Q 3147 4750 3647 4120 Q 4147 3491 4147 2328 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-5" onclick="(()=&gt;{console.log('text 5 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(383.164961 245.359486) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-6" onclick="(()=&gt;{console.log('text 6 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(197.191669 463.022351) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-35"
d="M 678 4666 L 3669 4666 L 3669 3781 L 1638 3781 L 1638 3059 Q 1775 3097 1914 3117 Q 2053 3138 2203 3138 Q 3056 3138 3531 2711 Q 4006 2284 4006 1522 Q 4006 766 3489 337 Q 2972 -91 2053 -91 Q 1656 -91 1267 -14 Q 878 63 494 219 L 494 1166 Q 875 947 1217 837 Q 1559 728 1863 728 Q 2300 728 2551 942 Q 2803 1156 2803 1522 Q 2803 1891 2551 2103 Q 2300 2316 1863 2316 Q 1603 2316 1309 2248 Q 1016 2181 678 2041 L 678 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-7" onclick="(()=&gt;{console.log('text 7 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(152.092875 295.851586) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-36"
d="M 2316 2303 Q 2000 2303 1842 2098 Q 1684 1894 1684 1484 Q 1684 1075 1842 870 Q 2000 666 2316 666 Q 2634 666 2792 870 Q 2950 1075 2950 1484 Q 2950 1894 2792 2098 Q 2634 2303 2316 2303 z M 3803 4544 L 3803 3681 Q 3506 3822 3243 3889 Q 2981 3956 2731 3956 Q 2194 3956 1894 3657 Q 1594 3359 1544 2772 Q 1750 2925 1990 3001 Q 2231 3078 2516 3078 Q 3231 3078 3670 2659 Q 4109 2241 4109 1563 Q 4109 813 3618 361 Q 3128 -91 2303 -91 Q 1394 -91 895 523 Q 397 1138 397 2266 Q 397 3422 980 4083 Q 1563 4744 2578 4744 Q 2900 4744 3203 4694 Q 3506 4644 3803 4544 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-8" onclick="(()=&gt;{console.log('text 8 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(415.720934 222.332954) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-9" onclick="(()=&gt;{console.log('text 9 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(337.329637 110.918523) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-10" onclick="(()=&gt;{console.log('text 10 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(316.995629 222.507171) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-39"
d="M 641 103 L 641 966 Q 928 831 1190 764 Q 1453 697 1709 697 Q 2247 697 2547 995 Q 2847 1294 2900 1881 Q 2688 1725 2447 1647 Q 2206 1569 1925 1569 Q 1209 1569 770 1986 Q 331 2403 331 3084 Q 331 3838 820 4291 Q 1309 4744 2131 4744 Q 3044 4744 3544 4128 Q 4044 3513 4044 2388 Q 4044 1231 3459 570 Q 2875 -91 1856 -91 Q 1528 -91 1228 -42 Q 928 6 641 103 z M 2125 2350 Q 2441 2350 2600 2554 Q 2759 2759 2759 3169 Q 2759 3575 2600 3781 Q 2441 3988 2125 3988 Q 1809 3988 1650 3781 Q 1491 3575 1491 3169 Q 1491 2759 1650 2554 Q 1809 2350 2125 2350 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-11" onclick="(()=&gt;{console.log('text 11 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(354.725926 205.013187) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-12" onclick="(()=&gt;{console.log('text 12 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(358.281208 151.471241) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-13" onclick="(()=&gt;{console.log('text 13 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(333.112734 173.726092) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-14" onclick="(()=&gt;{console.log('text 14 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(159.046959 329.273098) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-15" onclick="(()=&gt;{console.log('text 15 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(253.761934 185.199227) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-16" onclick="(()=&gt;{console.log('text 16 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(180.944121 266.046391) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-33"
d="M 2981 2516 Q 3453 2394 3698 2092 Q 3944 1791 3944 1325 Q 3944 631 3412 270 Q 2881 -91 1863 -91 Q 1503 -91 1142 -33 Q 781 25 428 141 L 428 1069 Q 766 900 1098 814 Q 1431 728 1753 728 Q 2231 728 2486 893 Q 2741 1059 2741 1369 Q 2741 1688 2480 1852 Q 2219 2016 1709 2016 L 1228 2016 L 1228 2791 L 1734 2791 Q 2188 2791 2409 2933 Q 2631 3075 2631 3366 Q 2631 3634 2415 3781 Q 2200 3928 1806 3928 Q 1516 3928 1219 3862 Q 922 3797 628 3669 L 628 4550 Q 984 4650 1334 4700 Q 1684 4750 2022 4750 Q 2931 4750 3382 4451 Q 3834 4153 3834 3553 Q 3834 3144 3618 2883 Q 3403 2622 2981 2516 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-17" onclick="(()=&gt;{console.log('text 17 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(269.606655 465.933789) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-18" onclick="(()=&gt;{console.log('text 18 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(325.583218 272.653614) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-19" onclick="(()=&gt;{console.log('text 19 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(408.411614 350.310389) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-20" onclick="(()=&gt;{console.log('text 20 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(326.31723 341.408828) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-21" onclick="(()=&gt;{console.log('text 21 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(195.638162 351.242444) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-22" onclick="(()=&gt;{console.log('text 22 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(279.71061 367.236222) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-23" onclick="(()=&gt;{console.log('text 23 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(267.849143 392.30993) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-24" onclick="(()=&gt;{console.log('text 24 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(284.880842 424.704655) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-25" onclick="(()=&gt;{console.log('text 25 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(415.504337 381.632604) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-26" onclick="(()=&gt;{console.log('text 26 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(196.864977 220.377413) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-27" onclick="(()=&gt;{console.log('text 27 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(234.462583 357.393433) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-28" onclick="(()=&gt;{console.log('text 28 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(145.697818 426.464523) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-29" onclick="(()=&gt;{console.log('text 29 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(290.510319 335.777161) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-30" onclick="(()=&gt;{console.log('text 30 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(269.445395 310.291491) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-31" onclick="(()=&gt;{console.log('text 31 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(251.655337 429.737528) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-32" onclick="(()=&gt;{console.log('text 32 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(293.568025 261.436163) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-33" onclick="(()=&gt;{console.log('text 33 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(246.547954 266.344409) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-34" onclick="(()=&gt;{console.log('text 34 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(312.918088 406.332457) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-35" onclick="(()=&gt;{console.log('text 35 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(337.400892 383.442961) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-36" onclick="(()=&gt;{console.log('text 36 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(395.88896 406.065981) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-37" onclick="(()=&gt;{console.log('text 37 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(347.38477 238.99916) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-38" onclick="(()=&gt;{console.log('text 38 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(185.584411 411.315605) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-39" onclick="(()=&gt;{console.log('text 39 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(289.181126 197.86249) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-40" onclick="(()=&gt;{console.log('text 40 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(311.726285 371.889731) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-41" onclick="(()=&gt;{console.log('text 41 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(220.340369 413.270465) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-42" onclick="(()=&gt;{console.log('text 42 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(342.876801 422.653917) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-43" onclick="(()=&gt;{console.log('text 43 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(363.771039 368.733221) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-44" onclick="(()=&gt;{console.log('text 44 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(221.953701 439.459389) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-45" onclick="(()=&gt;{console.log('text 45 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(253.157028 477.463977) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-46" onclick="(()=&gt;{console.log('text 46 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(409.211471 262.775596) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-47" onclick="(()=&gt;{console.log('text 47 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(392.258494 438.041697) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-48" onclick="(()=&gt;{console.log('text 48 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(273.912603 286.090784) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-49" onclick="(()=&gt;{console.log('text 49 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(213.773188 264.727971) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-female-50" onclick="(()=&gt;{console.log('text 50 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pe3de578e26)">
<g transform="translate(301.181549 458.138582) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="pe3de578e26">
<rect x="124.405104" y="69.12" width="341.589792" height="443.52" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 56 KiB

View File

@ -0,0 +1,898 @@
<?xml version='1.0' encoding='utf-8'?>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:ns2="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:xlink="http://www.w3.org/1999/xlink" width="100%" height="100%" viewBox="100 60 420 450" version="1.1">
<metadata>
<rdf:RDF>
<ns2:Work>
<dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:date>2023-11-19T11:21:55.705408</dc:date>
<dc:format>image/svg+xml</dc:format>
<dc:creator>
<ns2:Agent>
<dc:title>Matplotlib v3.7.1, https://matplotlib.org/</dc:title>
</ns2:Agent>
</dc:creator>
</ns2:Work>
</rdf:RDF>
</metadata>
<defs>
<style type="text/css">
* {
stroke-linejoin: round;
stroke-linecap: butt
}
</style>
<style type="text/css">
.beatrice-node-pointer {
cursor: pointer;
}
.beatrice-node-pointer:hover {
stroke: gray;
}
.beatrice-node-pointer-selected {
stroke: #ef6767c2;
stroke-width: 3
}
.beatrice-text-pointer {
cursor: pointer;
pointer-events: none
}
.beatrice-text-pointer:hover {
/* ホバー時のスタイルは既に設定されたスタイルと異なる特定の属性を変更することができます。 */
}
</style>
</defs>
<g id="figure_1">
<g id="patch_1">
<path d="M 0 576 L 576 576 L 576 0 L 0 0 z " style="fill: #ffffff" />
</g>
<g id="axes_1">
<g id="LineCollection_1">
<path d="M 383.475478 335.382791 L 350.123561 336.312105 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 383.475478 335.382791 L 393.562573 295.917472 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 383.475478 335.382791 L 396.396073 371.656412 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 395.592267 184.349842 L 344.302973 166.290216 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 166.614267 246.553188 L 214.405523 244.575019 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 362.886037 395.352171 L 389.299516 416.267064 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 362.886037 395.352171 L 367.134249 434.454954 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 362.886037 395.352171 L 396.396073 371.656412 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 362.886037 395.352171 L 321.091057 403.95329 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 291.699254 114.456198 L 287.429936 148.935339 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 309.72476 346.813492 L 326.464644 303.679747 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 396.396073 371.656412 L 422.276969 403.842356 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 396.396073 371.656412 L 419.504487 334.14189 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 311.713 188.802087 L 278.840744 190.572938 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 311.713 188.802087 L 287.429936 148.935339 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 311.713 188.802087 L 344.302973 166.290216 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 213.805036 285.720019 L 216.196468 317.113868 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 213.805036 285.720019 L 241.321249 255.242558 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 213.805036 285.720019 L 169.41455 268.66905 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 326.464644 303.679747 L 341.073251 272.287852 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 326.464644 303.679747 L 281.878613 277.846944 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 326.464644 303.679747 L 350.123561 336.312105 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 468.104517 290.196764 L 453.314054 329.209099 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 252.522958 107.607273 L 287.429936 148.935339 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 241.817 158.353487 L 278.840744 190.572938 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 278.840744 190.572938 L 264.569363 223.030096 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 190.32114 223.314542 L 214.405523 244.575019 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 233.41271 348.401671 L 216.196468 317.113868 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 122.295483 352.502553 L 162.445269 355.484449 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 158.624602 400.46174 L 162.445269 355.484449 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 214.405523 244.575019 L 241.321249 255.242558 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 214.405523 244.575019 L 207.563253 203.013335 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 264.569363 223.030096 L 298.808991 236.296491 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 264.569363 223.030096 L 241.321249 255.242558 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 264.569363 223.030096 L 236.649711 206.251683 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 220.79649 394.869471 L 213.931911 434.927829 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 220.79649 394.869471 L 208.453065 361.465389 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 241.321249 255.242558 L 281.878613 277.846944 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 257.120877 296.156882 L 281.878613 277.846944 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 453.314054 329.209099 L 419.504487 334.14189 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 287.180764 309.56402 L 281.878613 277.846944 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 287.429936 148.935339 L 321.071206 134.027026 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 300.632415 433.812968 L 321.091057 403.95329 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 216.196468 317.113868 L 181.944484 318.835753 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 216.196468 317.113868 L 208.453065 361.465389 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 419.504487 334.14189 L 436.061109 363.566053 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 366.514998 474.152727 L 367.134249 434.454954 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
<path d="M 208.453065 361.465389 L 162.445269 355.484449 " clip-path="url(#pd42c8a995e)"
style="fill: none; stroke: #808080" />
</g>
<g id="PathCollection_1">
<defs>
<path id="C0_0_3858269516"
d="M 0 11.18034 C 2.965061 11.18034 5.80908 10.002309 7.905694 7.905694 C 10.002309 5.80908 11.18034 2.965061 11.18034 -0 C 11.18034 -2.965061 10.002309 -5.80908 7.905694 -7.905694 C 5.80908 -10.002309 2.965061 -11.18034 0 -11.18034 C -2.965061 -11.18034 -5.80908 -10.002309 -7.905694 -7.905694 C -10.002309 -5.80908 -11.18034 -2.965061 -11.18034 0 C -11.18034 2.965061 -10.002309 5.80908 -7.905694 7.905694 C -5.80908 10.002309 -2.965061 11.18034 0 11.18034 z " />
</defs>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-0" onclick="(()=&gt;{console.log('node 0')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="383.475478" y="335.382791" style="fill: #fde2bb" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-1" onclick="(()=&gt;{console.log('node 1')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="393.562573" y="295.917472" style="fill: #fdba68" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-2" onclick="(()=&gt;{console.log('node 2')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="395.592267" y="184.349842" style="fill: #fbe9cf" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-3" onclick="(()=&gt;{console.log('node 3')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="166.614267" y="246.553188" style="fill: #7e70ab" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-4" onclick="(()=&gt;{console.log('node 4')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="362.886037" y="395.352171" style="fill: #e8e9f1" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-5" onclick="(()=&gt;{console.log('node 5')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="291.699254" y="114.456198" style="fill: #f9b158" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-6" onclick="(()=&gt;{console.log('node 6')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="309.72476" y="346.813492" style="fill: #e4e5f0" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-7" onclick="(()=&gt;{console.log('node 7')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="396.396073" y="371.656412" style="fill: #fdcc8c" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-8" onclick="(()=&gt;{console.log('node 8')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="311.713" y="188.802087" style="fill: #fedeb3" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-9" onclick="(()=&gt;{console.log('node 9')})()"
class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="213.805036" y="285.720019" style="fill: #bab5d7" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-10"
onclick="(()=&gt;{console.log('node 10')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="326.464644" y="303.679747" style="fill: #eaebf2" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-11"
onclick="(()=&gt;{console.log('node 11')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="468.104517" y="290.196764" style="fill: #f7f7f6" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-12"
onclick="(()=&gt;{console.log('node 12')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="169.41455" y="268.66905" style="fill: #dfe1ee" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-13"
onclick="(()=&gt;{console.log('node 13')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="252.522958" y="107.607273" style="fill: #eff0f4" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-14"
onclick="(()=&gt;{console.log('node 14')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="241.817" y="158.353487" style="fill: #e58a20" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-15"
onclick="(()=&gt;{console.log('node 15')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="278.840744" y="190.572938" style="fill: #fedbac" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-16"
onclick="(()=&gt;{console.log('node 16')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="190.32114" y="223.314542" style="fill: #dfe1ee" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-17"
onclick="(()=&gt;{console.log('node 17')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="233.41271" y="348.401671" style="fill: #c3c0dd" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-18"
onclick="(()=&gt;{console.log('node 18')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="122.295483" y="352.502553" style="fill: #fed8a6" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-19"
onclick="(()=&gt;{console.log('node 19')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="158.624602" y="400.46174" style="fill: #f7f6f3" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-20"
onclick="(()=&gt;{console.log('node 20')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="214.405523" y="244.575019" style="fill: #f9f2e9" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-21"
onclick="(()=&gt;{console.log('node 21')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="264.569363" y="223.030096" style="fill: #faecd7" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-22"
onclick="(()=&gt;{console.log('node 22')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="220.79649" y="394.869471" style="fill: #fbead2" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-23"
onclick="(()=&gt;{console.log('node 23')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="344.302973" y="166.290216" style="fill: #feddaf" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-24"
onclick="(()=&gt;{console.log('node 24')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="241.321249" y="255.242558" style="fill: #c3c0dd" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-25"
onclick="(()=&gt;{console.log('node 25')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="257.120877" y="296.156882" style="fill: #f9f0e4" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-26"
onclick="(()=&gt;{console.log('node 26')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="207.563253" y="203.013335" style="fill: #fbebd5" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-27"
onclick="(()=&gt;{console.log('node 27')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="453.314054" y="329.209099" style="fill: #fdc47b" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-28"
onclick="(()=&gt;{console.log('node 28')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="350.123561" y="336.312105" style="fill: #fbe9cf" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-29"
onclick="(()=&gt;{console.log('node 29')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="287.180764" y="309.56402" style="fill: #f7f6f3" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-30"
onclick="(()=&gt;{console.log('node 30')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="287.429936" y="148.935339" style="fill: #fbebd5" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-31"
onclick="(()=&gt;{console.log('node 31')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="281.878613" y="277.846944" style="fill: #d1d1e6" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-32"
onclick="(()=&gt;{console.log('node 32')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="300.632415" y="433.812968" style="fill: #fde2bb" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-33"
onclick="(()=&gt;{console.log('node 33')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="216.196468" y="317.113868" style="fill: #dddfed" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-34"
onclick="(()=&gt;{console.log('node 34')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="419.504487" y="334.14189" style="fill: #fdc57f" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-35"
onclick="(()=&gt;{console.log('node 35')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="321.071206" y="134.027026" style="fill: #fee0b6" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-36"
onclick="(()=&gt;{console.log('node 36')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="366.514998" y="474.152727" style="fill: #fdbd6e" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-37"
onclick="(()=&gt;{console.log('node 37')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="208.453065" y="361.465389" style="fill: #cccbe3" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-38"
onclick="(()=&gt;{console.log('node 38')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="236.649711" y="206.251683" style="fill: #faecd7" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-39"
onclick="(()=&gt;{console.log('node 39')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="298.808991" y="236.296491" style="fill: #fdc57f" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-40"
onclick="(()=&gt;{console.log('node 40')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="181.944484" y="318.835753" style="fill: #f9f0e4" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-41"
onclick="(()=&gt;{console.log('node 41')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="367.134249" y="434.454954" style="fill: #f6f6f7" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-42"
onclick="(()=&gt;{console.log('node 42')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="422.276969" y="403.842356" style="fill: #fdbf72" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-43"
onclick="(()=&gt;{console.log('node 43')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="321.091057" y="403.95329" style="fill: #f8f5f1" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-44"
onclick="(()=&gt;{console.log('node 44')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="162.445269" y="355.484449" style="fill: #eaebf2" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-45"
onclick="(()=&gt;{console.log('node 45')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="341.073251" y="272.287852" style="fill: #f6aa4f" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-46"
onclick="(()=&gt;{console.log('node 46')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="389.299516" y="416.267064" style="fill: #de8013" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-47"
onclick="(()=&gt;{console.log('node 47')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="213.931911" y="434.927829" style="fill: #fbb55e" />
</g>
<g clip-path="url(#pd42c8a995e)" id="beatrice-node-male-48"
onclick="(()=&gt;{console.log('node 48')})()" class="beatrice-node-pointer">
<use xlink:href="#C0_0_3858269516" x="436.061109" y="363.566053" style="fill: #ebecf3" />
</g>
</g>
<g id="beatrice-text-male-0" onclick="(()=&gt;{console.log('text 0 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(379.30079 338.694041) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-31"
d="M 750 831 L 1813 831 L 1813 3847 L 722 3622 L 722 4441 L 1806 4666 L 2950 4666 L 2950 831 L 4013 831 L 4013 0 L 750 0 L 750 831 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
</g>
</g>
</g>
<g id="beatrice-text-male-1" onclick="(()=&gt;{console.log('text 1 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(389.387885 299.228722) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-33"
d="M 2981 2516 Q 3453 2394 3698 2092 Q 3944 1791 3944 1325 Q 3944 631 3412 270 Q 2881 -91 1863 -91 Q 1503 -91 1142 -33 Q 781 25 428 141 L 428 1069 Q 766 900 1098 814 Q 1431 728 1753 728 Q 2231 728 2486 893 Q 2741 1059 2741 1369 Q 2741 1688 2480 1852 Q 2219 2016 1709 2016 L 1228 2016 L 1228 2791 L 1734 2791 Q 2188 2791 2409 2933 Q 2631 3075 2631 3366 Q 2631 3634 2415 3781 Q 2200 3928 1806 3928 Q 1516 3928 1219 3862 Q 922 3797 628 3669 L 628 4550 Q 984 4650 1334 4700 Q 1684 4750 2022 4750 Q 2931 4750 3382 4451 Q 3834 4153 3834 3553 Q 3834 3144 3618 2883 Q 3403 2622 2981 2516 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-33" />
</g>
</g>
</g>
<g id="beatrice-text-male-2" onclick="(()=&gt;{console.log('text 2 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(391.41758 187.661092) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-35"
d="M 678 4666 L 3669 4666 L 3669 3781 L 1638 3781 L 1638 3059 Q 1775 3097 1914 3117 Q 2053 3138 2203 3138 Q 3056 3138 3531 2711 Q 4006 2284 4006 1522 Q 4006 766 3489 337 Q 2972 -91 2053 -91 Q 1656 -91 1267 -14 Q 878 63 494 219 L 494 1166 Q 875 947 1217 837 Q 1559 728 1863 728 Q 2300 728 2551 942 Q 2803 1156 2803 1522 Q 2803 1891 2551 2103 Q 2300 2316 1863 2316 Q 1603 2316 1309 2248 Q 1016 2181 678 2041 L 678 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-35" />
</g>
</g>
</g>
<g id="beatrice-text-male-3" onclick="(()=&gt;{console.log('text 3 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(162.43958 249.864438) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-36"
d="M 2316 2303 Q 2000 2303 1842 2098 Q 1684 1894 1684 1484 Q 1684 1075 1842 870 Q 2000 666 2316 666 Q 2634 666 2792 870 Q 2950 1075 2950 1484 Q 2950 1894 2792 2098 Q 2634 2303 2316 2303 z M 3803 4544 L 3803 3681 Q 3506 3822 3243 3889 Q 2981 3956 2731 3956 Q 2194 3956 1894 3657 Q 1594 3359 1544 2772 Q 1750 2925 1990 3001 Q 2231 3078 2516 3078 Q 3231 3078 3670 2659 Q 4109 2241 4109 1563 Q 4109 813 3618 361 Q 3128 -91 2303 -91 Q 1394 -91 895 523 Q 397 1138 397 2266 Q 397 3422 980 4083 Q 1563 4744 2578 4744 Q 2900 4744 3203 4694 Q 3506 4644 3803 4544 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-36" />
</g>
</g>
</g>
<g id="beatrice-text-male-4" onclick="(()=&gt;{console.log('text 4 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(358.71135 398.663421) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-39"
d="M 641 103 L 641 966 Q 928 831 1190 764 Q 1453 697 1709 697 Q 2247 697 2547 995 Q 2847 1294 2900 1881 Q 2688 1725 2447 1647 Q 2206 1569 1925 1569 Q 1209 1569 770 1986 Q 331 2403 331 3084 Q 331 3838 820 4291 Q 1309 4744 2131 4744 Q 3044 4744 3544 4128 Q 4044 3513 4044 2388 Q 4044 1231 3459 570 Q 2875 -91 1856 -91 Q 1528 -91 1228 -42 Q 928 6 641 103 z M 2125 2350 Q 2441 2350 2600 2554 Q 2759 2759 2759 3169 Q 2759 3575 2600 3781 Q 2441 3988 2125 3988 Q 1809 3988 1650 3781 Q 1491 3575 1491 3169 Q 1491 2759 1650 2554 Q 1809 2350 2125 2350 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-39" />
</g>
</g>
</g>
<g id="beatrice-text-male-5" onclick="(()=&gt;{console.log('text 5 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(283.349879 117.767448) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-6" onclick="(()=&gt;{console.log('text 6 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(301.375385 350.124742) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-32"
d="M 1844 884 L 3897 884 L 3897 0 L 506 0 L 506 884 L 2209 2388 Q 2438 2594 2547 2791 Q 2656 2988 2656 3200 Q 2656 3528 2436 3728 Q 2216 3928 1850 3928 Q 1569 3928 1234 3808 Q 900 3688 519 3450 L 519 4475 Q 925 4609 1322 4679 Q 1719 4750 2100 4750 Q 2938 4750 3402 4381 Q 3866 4013 3866 3353 Q 3866 2972 3669 2642 Q 3472 2313 2841 1759 L 1844 884 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-7" onclick="(()=&gt;{console.log('text 7 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(388.046698 374.967662) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-8" onclick="(()=&gt;{console.log('text 8 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(303.363625 192.113337) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-30"
d="M 2944 2338 Q 2944 3213 2780 3570 Q 2616 3928 2228 3928 Q 1841 3928 1675 3570 Q 1509 3213 1509 2338 Q 1509 1453 1675 1090 Q 1841 728 2228 728 Q 2613 728 2778 1090 Q 2944 1453 2944 2338 z M 4147 2328 Q 4147 1169 3647 539 Q 3147 -91 2228 -91 Q 1306 -91 806 539 Q 306 1169 306 2328 Q 306 3491 806 4120 Q 1306 4750 2228 4750 Q 3147 4750 3647 4120 Q 4147 3491 4147 2328 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-9" onclick="(()=&gt;{console.log('text 9 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(205.455661 289.031269) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-10" onclick="(()=&gt;{console.log('text 10 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(318.115269 306.990997) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-11" onclick="(()=&gt;{console.log('text 11 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(459.755142 293.508014) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-12" onclick="(()=&gt;{console.log('text 12 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(161.065175 271.9803) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-38"
d="M 2228 2088 Q 1891 2088 1709 1903 Q 1528 1719 1528 1375 Q 1528 1031 1709 848 Q 1891 666 2228 666 Q 2563 666 2741 848 Q 2919 1031 2919 1375 Q 2919 1722 2741 1905 Q 2563 2088 2228 2088 z M 1350 2484 Q 925 2613 709 2878 Q 494 3144 494 3541 Q 494 4131 934 4440 Q 1375 4750 2228 4750 Q 3075 4750 3515 4442 Q 3956 4134 3956 3541 Q 3956 3144 3739 2878 Q 3522 2613 3097 2484 Q 3572 2353 3814 2058 Q 4056 1763 4056 1313 Q 4056 619 3595 264 Q 3134 -91 2228 -91 Q 1319 -91 855 264 Q 391 619 391 1313 Q 391 1763 633 2058 Q 875 2353 1350 2484 z M 1631 3419 Q 1631 3141 1786 2991 Q 1941 2841 2228 2841 Q 2509 2841 2662 2991 Q 2816 3141 2816 3419 Q 2816 3697 2662 3845 Q 2509 3994 2228 3994 Q 1941 3994 1786 3844 Q 1631 3694 1631 3419 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-32" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-13" onclick="(()=&gt;{console.log('text 13 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(244.173583 110.918523) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-14" onclick="(()=&gt;{console.log('text 14 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(233.467625 161.664737) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-15" onclick="(()=&gt;{console.log('text 15 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(270.491369 193.884188) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-16" onclick="(()=&gt;{console.log('text 16 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(181.971765 226.625792) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-34"
d="M 2356 3675 L 1038 1722 L 2356 1722 L 2356 3675 z M 2156 4666 L 3494 4666 L 3494 1722 L 4159 1722 L 4159 850 L 3494 850 L 3494 0 L 2356 0 L 2356 850 L 288 850 L 288 1881 L 2156 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-17" onclick="(()=&gt;{console.log('text 17 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(225.063335 351.712921) scale(0.12 -0.12)">
<defs>
<path id="DejaVuSans-Bold-37"
d="M 428 4666 L 3944 4666 L 3944 3988 L 2125 0 L 953 0 L 2675 3781 L 428 3781 L 428 4666 z "
transform="scale(0.015625)" />
</defs>
<use xlink:href="#DejaVuSans-Bold-33" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-18" onclick="(()=&gt;{console.log('text 18 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(113.946108 355.813803) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-19" onclick="(()=&gt;{console.log('text 19 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(150.275227 403.77299) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-20" onclick="(()=&gt;{console.log('text 20 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(206.056148 247.886269) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-21" onclick="(()=&gt;{console.log('text 21 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(256.219988 226.341346) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-22" onclick="(()=&gt;{console.log('text 22 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(212.447115 398.180721) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-23" onclick="(()=&gt;{console.log('text 23 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(335.953598 169.601466) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-24" onclick="(()=&gt;{console.log('text 24 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(232.971874 258.553808) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-25" onclick="(()=&gt;{console.log('text 25 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(248.771502 299.468132) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-34" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-26" onclick="(()=&gt;{console.log('text 26 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(199.213878 206.324585) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-27" onclick="(()=&gt;{console.log('text 27 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(444.964679 332.520349) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-32" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-28" onclick="(()=&gt;{console.log('text 28 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(341.774186 339.623355) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-35" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-29" onclick="(()=&gt;{console.log('text 29 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(278.831389 312.87527) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-36" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-30" onclick="(()=&gt;{console.log('text 30 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(279.080561 152.246589) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-31" onclick="(()=&gt;{console.log('text 31 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(273.529238 281.158194) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-32" onclick="(()=&gt;{console.log('text 32 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(292.28304 437.124218) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-33" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-33" onclick="(()=&gt;{console.log('text 33 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(207.847093 320.425118) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-34" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-34" onclick="(()=&gt;{console.log('text 34 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(411.155112 337.45314) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-35" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-35" onclick="(()=&gt;{console.log('text 35 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(312.721831 137.338276) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-36" onclick="(()=&gt;{console.log('text 36 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(358.165623 477.463977) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-37" onclick="(()=&gt;{console.log('text 37 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(200.10369 364.776639) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-38" onclick="(()=&gt;{console.log('text 38 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(228.300336 209.562933) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-37" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-39" onclick="(()=&gt;{console.log('text 39 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(290.459616 239.607741) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-40" onclick="(()=&gt;{console.log('text 40 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(173.595109 322.147003) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-31" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-41" onclick="(()=&gt;{console.log('text 41 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(358.784874 437.766204) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-36" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-42" onclick="(()=&gt;{console.log('text 42 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(413.927594 407.153606) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-43" onclick="(()=&gt;{console.log('text 43 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(312.741682 407.26454) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-44" onclick="(()=&gt;{console.log('text 44 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(154.095894 358.795699) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-38" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-45" onclick="(()=&gt;{console.log('text 45 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(332.723876 275.599102) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-37" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-46" onclick="(()=&gt;{console.log('text 46 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(380.950141 419.578314) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-38" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-47" onclick="(()=&gt;{console.log('text 47 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(205.582536 438.239079) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-39" />
<use xlink:href="#DejaVuSans-Bold-39" x="69.580078" />
</g>
</g>
</g>
<g id="beatrice-text-male-48" onclick="(()=&gt;{console.log('text 48 clicked')})()"
class="beatrice-text-pointer">
<g clip-path="url(#pd42c8a995e)">
<g transform="translate(423.537047 366.877303) scale(0.12 -0.12)">
<use xlink:href="#DejaVuSans-Bold-31" />
<use xlink:href="#DejaVuSans-Bold-30" x="69.580078" />
<use xlink:href="#DejaVuSans-Bold-30" x="139.160156" />
</g>
</g>
</g>
</g>
</g>
<defs>
<clipPath id="pd42c8a995e">
<rect x="85.985534" y="69.12" width="418.428931" height="443.52" />
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 54 KiB

View File

@ -21,8 +21,8 @@
{
"name": "configArea",
"options": {
"detectors": ["dio", "harvest", "crepe", "crepe_full", "crepe_tiny"],
"inputChunkNums": [8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048]
"detectors": ["dio", "harvest", "crepe", "crepe_full", "crepe_tiny", "rmvpe", "rmvpe_onnx", "fcpe"],
"inputChunkNums": [1, 2, 4, 6, 8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048, 4096, 8192, 16384]
}
}
]

View File

@ -0,0 +1 @@
onnxdirectML-cuda

View File

@ -0,0 +1 @@
web

View File

@ -0,0 +1 @@
{}

View File

@ -4,114 +4,122 @@ import { library } from "@fortawesome/fontawesome-svg-core";
import { fas } from "@fortawesome/free-solid-svg-icons";
import { far } from "@fortawesome/free-regular-svg-icons";
import { fab } from "@fortawesome/free-brands-svg-icons";
import { ErrorInfo, useEffect, useMemo, useState, } from "react";
import { ErrorInfo, useEffect, useMemo, useState } from "react";
import "./css/App.css"
import "./css/App.css";
import ErrorBoundary from "./001_provider/900_ErrorBoundary";
import { AppStateProvider } from "./001_provider/001_AppStateProvider";
import { AppRootProvider, useAppRoot } from "./001_provider/001_AppRootProvider";
import { useIndexedDB } from "@dannadori/voice-changer-client-js";
import { Demo } from "./components/demo/010_Demo";
import { useMessageBuilder } from "./hooks/useMessageBuilder";
import { removeDB as webDBRemove } from "@dannadori/voice-changer-js";
library.add(fas, far, fab);
const container = document.getElementById("app")!;
const root = createRoot(container);
const App = () => {
const { appGuiSettingState } = useAppRoot()
const { appGuiSettingState } = useAppRoot();
const front = useMemo(() => {
if (appGuiSettingState.appGuiSetting.type == "demo") {
return <Demo></Demo>
return <Demo></Demo>;
} else {
return <>unknown gui type. {appGuiSettingState.appGuiSetting.type}</>
return <>unknown gui type. {appGuiSettingState.appGuiSetting.type}</>;
}
}, [appGuiSettingState.appGuiSetting.type])
}, [appGuiSettingState.appGuiSetting.type]);
return (
<>
{front}
</>
)
}
return <>{front}</>;
};
const AppStateWrapper = () => {
const { appGuiSettingState, getGUISetting } = useAppRoot()
const messageBuilderState = useMessageBuilder()
const { appGuiSettingState, getGUISetting } = useAppRoot();
const messageBuilderState = useMessageBuilder();
// エラーメッセージ登録
useMemo(() => {
messageBuilderState.setMessage(__filename, "Problem", { "ja": "ちょっと問題が起きたみたいです。", "en": "Looks like there's a bit of a problem." })
messageBuilderState.setMessage(__filename, "Problem-sub1", { "ja": "このアプリで管理している情報をクリアすると回復する場合があります。", "en": "" })
messageBuilderState.setMessage(__filename, "Problem-sub2", { "ja": "下記のボタンを押して情報をクリアします。", "en": "If you clear the information being managed by this app, it may be recoverable." })
messageBuilderState.setMessage(__filename, "Problem-action1", { "ja": "アプリを初期化", "en": "Initialize" })
messageBuilderState.setMessage(__filename, "Problem-action2", { "ja": "初期化せずリロード", "en": "Reload without initialize" })
}, [])
messageBuilderState.setMessage(__filename, "Problem", { ja: "ちょっと問題が起きたみたいです。", en: "Looks like there's a bit of a problem." });
messageBuilderState.setMessage(__filename, "Problem-sub1", { ja: "このアプリで管理している情報をクリアすると回復する場合があります。", en: "" });
messageBuilderState.setMessage(__filename, "Problem-sub2", { ja: "下記のボタンを押して情報をクリアします。", en: "If you clear the information being managed by this app, it may be recoverable." });
messageBuilderState.setMessage(__filename, "Problem-action1", { ja: "アプリを初期化", en: "Initialize" });
messageBuilderState.setMessage(__filename, "Problem-action2", { ja: "初期化せずリロード", en: "Reload without initialize" });
}, []);
// エラーバウンダリー設定
const [error, setError] = useState<{ error: Error, errorInfo: ErrorInfo }>()
const { removeDB } = useIndexedDB({ clientType: null })
const [error, setError] = useState<{ error: Error; errorInfo: ErrorInfo | null; reason: any }>();
const { removeDB } = useIndexedDB({ clientType: null });
const errorComponent = useMemo(() => {
const errorName = error?.error.name || "no error name"
const errorMessage = error?.error.message || "no error message"
const errorInfos = (error?.errorInfo.componentStack || "no error stack").split("\n")
const errorName = error?.error.name || "no error name";
const errorMessage = error?.error.message || "no error message";
const errorInfos = (error?.errorInfo?.componentStack || "no error stack").split("\n");
const reasonMessage = (error?.reason || "no reason").toString();
const reasonStack = (error?.reason.stack || "no stack").split("\n") as string[];
const onClearCacheClicked = async () => {
await removeDB()
await removeDB();
await webDBRemove();
location.reload();
}
};
const onReloadClicked = () => {
location.reload();
}
};
return (
<div className="error-container">
<div className="top-error-message">
{messageBuilderState.getMessage(__filename, "Problem")}
</div>
<div className="top-error-message">{messageBuilderState.getMessage(__filename, "Problem")}</div>
<div className="top-error-description">
<p> {messageBuilderState.getMessage(__filename, "Problem-sub1")}</p>
<p> {messageBuilderState.getMessage(__filename, "Problem-sub2")}</p>
<p><button onClick={onClearCacheClicked}>{messageBuilderState.getMessage(__filename, "Problem-action1")}</button></p>
<p><button onClick={onReloadClicked}>{messageBuilderState.getMessage(__filename, "Problem-action2")}</button></p>
<p>
<button onClick={onClearCacheClicked}>{messageBuilderState.getMessage(__filename, "Problem-action1")}</button>
</p>
<p>
<button onClick={onReloadClicked}>{messageBuilderState.getMessage(__filename, "Problem-action2")}</button>
</p>
</div>
<div className="error-detail">
<div className="error-name">
{errorName}
</div>
<div className="error-message">
{errorMessage}
</div>
<div className="error-name">{errorName}</div>
<div className="error-message">{errorMessage}</div>
<div className="error-info-container">
{errorInfos.map(x => {
return <div className="error-info-line" key={x}>{x}</div>
{errorInfos.map((x) => {
return (
<div className="error-info-line" key={x}>
{x}
</div>
);
})}
</div>
</div>
<div className="error-detail">
<div className="error-name">{reasonMessage}</div>
<div className="error-message"></div>
<div className="error-info-container">
{reasonStack.map((x) => {
return (
<div className="error-info-line" key={x}>
{x}
</div>
);
})}
</div>
</div>
</div>
)
}, [error])
const updateError = (error: Error, errorInfo: React.ErrorInfo) => {
console.log("error compo", error, errorInfo)
setError({ error, errorInfo })
}
);
}, [error]);
const updateError = (error: Error, errorInfo: React.ErrorInfo | null, reason: any) => {
setError({ error, errorInfo, reason });
};
useEffect(() => {
const loadDefaultModelType = async () => {
getGUISetting()
}
loadDefaultModelType()
}, [])
getGUISetting();
};
loadDefaultModelType();
}, []);
if (!appGuiSettingState.guiSettingLoaded) {
return <>loading...</>
return <>loading...</>;
} else {
return (
<ErrorBoundary fallback={errorComponent} onError={updateError}>
@ -119,14 +127,12 @@ const AppStateWrapper = () => {
<App></App>
</AppStateProvider>
</ErrorBoundary>
)
);
}
}
};
root.render(
<AppRootProvider>
<AppStateWrapper></AppStateWrapper>
</AppRootProvider>
</AppRootProvider>,
);

View File

@ -0,0 +1,299 @@
import { ClientState, WebModelSlot } from "@dannadori/voice-changer-client-js";
import { VoiceChangerJSClientConfig, VoiceChangerJSClient, ProgressUpdateType, ProgreeeUpdateCallbcckInfo, VoiceChangerType, InputLengthKey, ResponseTimeInfo } from "@dannadori/voice-changer-js";
import { useEffect, useMemo, useRef, useState } from "react";
export type UseWebInfoProps = {
clientState: ClientState | null;
webEdition: boolean;
};
export const WebModelLoadingState = {
none: "none",
loading: "loading",
warmup: "warmup",
ready: "ready",
} as const;
export type WebModelLoadingState = (typeof WebModelLoadingState)[keyof typeof WebModelLoadingState];
export type VoiceChangerConfig = {
config: VoiceChangerJSClientConfig;
modelUrl: string;
portrait: string;
name: string;
termOfUse: string;
sampleRate: ModelSampleRateStr;
useF0: boolean;
inputLength: InputLengthKey;
progressCallback?: ((data: any) => void) | null;
};
export type WebInfoState = {
voiceChangerConfig: VoiceChangerConfig;
webModelLoadingState: WebModelLoadingState;
progressLoadPreprocess: number;
progressLoadVCModel: number;
progressWarmup: number;
webModelslot: WebModelSlot;
upkey: number;
responseTimeInfo: ResponseTimeInfo;
};
export type WebInfoStateAndMethod = WebInfoState & {
loadVoiceChanagerModel: () => Promise<void>;
setUpkey: (upkey: number) => void;
setVoiceChangerConfig: (voiceChangerType: VoiceChangerType, sampleRate: ModelSampleRateStr, useF0: boolean, inputLength: InputLengthKey) => void;
};
const ModelSampleRateStr = {
"40k": "40k",
"32k": "32k",
"16k": "16k",
} as const;
type ModelSampleRateStr = (typeof ModelSampleRateStr)[keyof typeof ModelSampleRateStr];
const noF0ModelUrl: { [modelType in VoiceChangerType]: { [inputLength in InputLengthKey]: { [sampleRate in ModelSampleRateStr]: string } } } = {
rvcv1: {
"24000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_40k_nof0_24000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_32k_nof0_24000.bin",
},
"16000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_40k_nof0_16000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_32k_nof0_16000.bin",
},
"12000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_40k_nof0_12000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_32k_nof0_12000.bin",
},
"8000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_40k_nof0_8000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_32k_nof0_8000.bin",
},
},
rvcv2: {
"24000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_40k_nof0_24000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_32k_nof0_24000.bin",
"16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_16k_nof0_24000.bin",
},
"16000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_40k_nof0_16000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_32k_nof0_16000.bin",
"16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_16k_nof0_16000.bin",
},
"12000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_40k_nof0_12000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_32k_nof0_12000.bin",
"16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_16k_nof0_12000.bin",
},
"8000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_40k_nof0_8000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_32k_nof0_8000.bin",
"16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_16k_nof0_8000.bin",
},
},
};
const f0ModelUrl: { [modelType in VoiceChangerType]: { [inputLength in InputLengthKey]: { [sampleRate in ModelSampleRateStr]: string } } } = {
rvcv1: {
"24000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_40k_f0_24000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_32k_f0_24000.bin",
},
"16000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_40k_f0_16000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_32k_f0_16000.bin",
},
"12000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_40k_f0_12000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_32k_f0_12000.bin",
},
"8000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_40k_f0_8000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv1_amitaro_v1_32k_f0_8000.bin",
},
},
rvcv2: {
"24000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_40k_f0_24000.bin",
// "32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_32k_f0_24000.bin",
"32k": "https://192.168.0.247:8080/models/rvcv2_exp_v2_32k_f0_24000.bin",
// "16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_16k_f0_24000.bin",
// "16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/vctk/rvcv2_vctk_v2_16k_f0_24000.bin",
"16k": "https://192.168.0.247:8080/models/rvcv2_vctk_v2_16k_f0_24000.bin",
},
"16000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_40k_f0_16000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_32k_f0_16000.bin",
// "16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_16k_f0_16000.bin",
"16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/vctk/rvcv2_vctk_v2_16k_f0_16000.bin",
},
"12000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_40k_f0_12000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_32k_f0_12000.bin",
// "16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_16k_f0_12000.bin",
"16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/vctk/rvcv2_vctk_v2_16k_f0_16000.bin",
},
"8000": {
"40k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_40k_f0_8000.bin",
"32k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_32k_f0_8000.bin",
// "16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/rvcv2_amitaro_v2_16k_f0_8000.bin",
"16k": "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/vctk/rvcv2_vctk_v2_16k_f0_8000.bin",
},
},
};
export const useWebInfo = (props: UseWebInfoProps): WebInfoStateAndMethod => {
const initVoiceChangerType: VoiceChangerType = "rvcv2";
const initInputLength: InputLengthKey = "24000";
const initUseF0 = true;
const initSampleRate: ModelSampleRateStr = "32k";
const progressCallback = (data: ProgreeeUpdateCallbcckInfo) => {
if (data.progressUpdateType === ProgressUpdateType.loadPreprocessModel) {
setProgressLoadPreprocess(data.progress);
} else if (data.progressUpdateType === ProgressUpdateType.loadVCModel) {
setProgressLoadVCModel(data.progress);
} else if (data.progressUpdateType === ProgressUpdateType.checkResponseTime) {
setProgressWarmup(data.progress);
}
};
const generateVoiceChangerConfig = (voiceChangerType: VoiceChangerType, sampleRate: ModelSampleRateStr, useF0: boolean, inputLength: InputLengthKey) => {
let modelUrl;
if (useF0) {
modelUrl = f0ModelUrl[voiceChangerType][inputLength][sampleRate];
} else {
modelUrl = noF0ModelUrl[voiceChangerType][inputLength][sampleRate];
}
const config: VoiceChangerConfig = {
config: {
voiceChangerType: voiceChangerType,
inputLength: inputLength,
baseUrl: window.location.origin,
inputSamplingRate: 48000,
outputSamplingRate: 48000,
},
modelUrl: modelUrl,
portrait: "https://huggingface.co/wok000/vcclient_model/resolve/main/web_model/v_01_alpha/amitaro/amitaro.png",
name: "あみたろ",
termOfUse: "https://huggingface.co/wok000/vcclient_model/raw/main/rvc/amitaro_contentvec_256/term_of_use.txt",
sampleRate: sampleRate,
useF0: useF0,
inputLength: inputLength,
progressCallback,
};
return config;
};
const [voiceChangerConfig, _setVoiceChangerConfig] = useState<VoiceChangerConfig>(generateVoiceChangerConfig(initVoiceChangerType, initSampleRate, initUseF0, initInputLength));
const [webModelLoadingState, setWebModelLoadingState] = useState<WebModelLoadingState>(WebModelLoadingState.none);
const [progressLoadPreprocess, setProgressLoadPreprocess] = useState<number>(0);
const [progressLoadVCModel, setProgressLoadVCModel] = useState<number>(0);
const [progressWarmup, setProgressWarmup] = useState<number>(0);
const [upkey, setUpkey] = useState<number>(0);
const [responseTimeInfo, setResponseTimeInfo] = useState<ResponseTimeInfo>({
responseTime: 0,
realDuration: 0,
rtf: 0,
});
const voiceChangerJSClient = useRef<VoiceChangerJSClient>();
const webModelslot: WebModelSlot = useMemo(() => {
return {
slotIndex: -1,
voiceChangerType: "WebModel",
name: voiceChangerConfig.name,
description: "",
credit: "",
termsOfUseUrl: voiceChangerConfig.termOfUse,
iconFile: voiceChangerConfig.portrait,
speakers: {},
defaultTune: 0,
modelType: "pyTorchRVCNono",
f0: voiceChangerConfig.useF0,
samplingRate: 0,
modelFile: "",
};
}, []);
const setVoiceChangerConfig = (voiceChangerType: VoiceChangerType, sampleRate: ModelSampleRateStr, useF0: boolean, inputLength: InputLengthKey) => {
const config = generateVoiceChangerConfig(voiceChangerType, sampleRate, useF0, inputLength);
_setVoiceChangerConfig(config);
};
// useEffect(() => {
// setVoiceChangerConfig({ ...voiceChangerConfig, progressCallback });
// }, []);
const loadVoiceChanagerModel = async () => {
if (!props.clientState) {
throw new Error("[useWebInfo] clientState is null");
}
if (!props.clientState.initialized) {
console.warn("[useWebInfo] clientState is not initialized yet");
return;
}
if (!props.webEdition) {
console.warn("[useWebInfo] this is not web edition");
return;
}
console.log("loadVoiceChanagerModel1", voiceChangerConfig);
setWebModelLoadingState("loading");
voiceChangerJSClient.current = new VoiceChangerJSClient();
await voiceChangerJSClient.current.initialize(voiceChangerConfig.config, voiceChangerConfig.modelUrl, voiceChangerConfig.progressCallback);
console.log("loadVoiceChanagerModel2");
// worm up
setWebModelLoadingState("warmup");
const warmupResult = await voiceChangerJSClient.current.checkResponseTime();
console.log("warmup result", warmupResult);
// check time
const responseTimeInfo = await voiceChangerJSClient.current.checkResponseTime();
console.log("responseTimeInfo", responseTimeInfo);
setResponseTimeInfo(responseTimeInfo);
props.clientState?.setInternalAudioProcessCallback({
processAudio: async (data: Uint8Array) => {
const audioF32 = new Float32Array(data.buffer);
const res = await voiceChangerJSClient.current!.convert(audioF32);
const audio = new Uint8Array(res[0].buffer);
if (res[1]) {
console.log("RESPONSE!", res[1]);
setResponseTimeInfo(res[1]);
}
return audio;
},
});
setWebModelLoadingState("ready");
};
useEffect(() => {
if (!voiceChangerJSClient.current) {
console.log("setupkey", voiceChangerJSClient.current);
return;
}
voiceChangerJSClient.current.setUpkey(upkey);
}, [upkey]);
useEffect(() => {
console.log("change voice ", voiceChangerConfig);
setProgressLoadPreprocess(0);
setProgressLoadVCModel(0);
setProgressWarmup(0);
loadVoiceChanagerModel();
}, [voiceChangerConfig, props.clientState?.initialized]);
return {
voiceChangerConfig,
webModelLoadingState,
progressLoadPreprocess,
progressLoadVCModel,
progressWarmup,
webModelslot,
upkey,
responseTimeInfo,
loadVoiceChanagerModel,
setUpkey,
setVoiceChangerConfig,
};
};

View File

@ -8,10 +8,10 @@ type Props = {
};
type AppRootValue = {
audioContextState: AudioConfigState
appGuiSettingState: AppGuiSettingStateAndMethod
getGUISetting: () => Promise<void>
}
audioContextState: AudioConfigState;
appGuiSettingState: AppGuiSettingStateAndMethod;
getGUISetting: () => Promise<void>;
};
const AppRootContext = React.createContext<AppRootValue | null>(null);
export const useAppRoot = (): AppRootValue => {
@ -23,17 +23,16 @@ export const useAppRoot = (): AppRootValue => {
};
export const AppRootProvider = ({ children }: Props) => {
const audioContextState = useAudioConfig()
const appGuiSettingState = useAppGuiSetting()
const audioContextState = useAudioConfig();
const appGuiSettingState = useAppGuiSetting();
const getGUISetting = async () => {
await appGuiSettingState.getAppGuiSetting(`/assets/gui_settings/GUI.json`)
}
await appGuiSettingState.getAppGuiSetting(`/assets/gui_settings/GUI.json`);
};
const providerValue: AppRootValue = {
audioContextState,
appGuiSettingState,
getGUISetting
getGUISetting,
};
return <AppRootContext.Provider value={providerValue}>{children}</AppRootContext.Provider>;
};

View File

@ -1,18 +1,22 @@
import { ClientState } from "@dannadori/voice-changer-client-js";
import { VoiceChangerJSClient } from "@dannadori/voice-changer-js";
import React, { useContext, useEffect, useRef } from "react";
import { ReactNode } from "react";
import { useVCClient } from "../001_globalHooks/001_useVCClient";
import { useAppRoot } from "./001_AppRootProvider";
import { useMessageBuilder } from "../hooks/useMessageBuilder";
import { WebInfoStateAndMethod, useWebInfo } from "../001_globalHooks/100_useWebInfo";
type Props = {
children: ReactNode;
};
type AppStateValue = ClientState & {
audioContext: AudioContext
initializedRef: React.MutableRefObject<boolean>
}
audioContext: AudioContext;
initializedRef: React.MutableRefObject<boolean>;
webInfoState: WebInfoStateAndMethod;
webEdition: boolean;
};
const AppStateContext = React.createContext<AppStateValue | null>(null);
export const useAppState = (): AppStateValue => {
@ -24,41 +28,51 @@ export const useAppState = (): AppStateValue => {
};
export const AppStateProvider = ({ children }: Props) => {
const appRoot = useAppRoot()
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext })
const messageBuilderState = useMessageBuilder()
const appRoot = useAppRoot();
const webEdition = appRoot.appGuiSettingState.edition.indexOf("web") >= 0;
const clientState = useVCClient({ audioContext: appRoot.audioContextState.audioContext });
const messageBuilderState = useMessageBuilder();
const webInfoState = useWebInfo({ clientState: clientState.clientState, webEdition: webEdition });
// const voiceChangerJSClient = useRef<VoiceChangerJSClient>();
useEffect(() => {
messageBuilderState.setMessage(__filename, "ioError", {
"ja": "エラーが頻発しています。対象としているフレームワークのモデルがロードされているか確認してください。",
"en": "Frequent errors occur. Please check if the model of the framework being targeted is loaded."
})
}, [])
ja: "エラーが頻発しています。対象としているフレームワークのモデルがロードされているか確認してください。",
en: "Frequent errors occur. Please check if the model of the framework being targeted is loaded.",
});
}, []);
const initializedRef = useRef<boolean>(false)
const initializedRef = useRef<boolean>(false);
useEffect(() => {
if (clientState.clientState.initialized) {
initializedRef.current = true
clientState.clientState.getInfo()
initializedRef.current = true;
clientState.clientState.getInfo();
// clientState.clientState.setVoiceChangerClientSetting({
// ...clientState.clientState.setting.voiceChangerClientSetting
// })
}
}, [clientState.clientState.initialized])
}, [clientState.clientState.initialized]);
useEffect(() => {
if (clientState.clientState.ioErrorCount > 100) {
alert(messageBuilderState.getMessage(__filename, "ioError"))
clientState.clientState.resetIoErrorCount()
alert(messageBuilderState.getMessage(__filename, "ioError"));
clientState.clientState.resetIoErrorCount();
}
}, [clientState.clientState.ioErrorCount]);
}, [clientState.clientState.ioErrorCount])
useEffect(() => {
if (appRoot.appGuiSettingState.edition.indexOf("web") >= 0 && clientState.clientState.initialized) {
clientState.clientState.setWorkletNodeSetting({ ...clientState.clientState.setting.workletNodeSetting, protocol: "internal" });
// webInfoState.loadVoiceChanagerModel(); // hook内でuseEffectでinvoke
}
}, [clientState.clientState.initialized]);
const providerValue: AppStateValue = {
audioContext: appRoot.audioContextState.audioContext!,
...clientState.clientState,
initializedRef,
webInfoState,
webEdition,
};
return <AppStateContext.Provider value={providerValue}>{children}</AppStateContext.Provider>;

View File

@ -1,17 +1,18 @@
import React, { ErrorInfo } from 'react';
import React, { ErrorInfo } from "react";
type ErrorBoundaryProps = {
children: React.ReactNode;
fallback: React.ReactNode;
onError?: (error: Error, errorInfo: React.ErrorInfo) => void;
}
onError: (error: Error, errorInfo: React.ErrorInfo | null, reason: any) => void;
};
type ErrorBoundaryState = {
hasError: boolean;
}
};
class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundaryState> {
private eventHandler: () => void
// @ts-ignore
private eventHandler: () => void;
constructor(props: ErrorBoundaryProps) {
super(props);
this.state = { hasError: false };
@ -24,24 +25,31 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
}
componentDidCatch(error: Error, errorInfo: ErrorInfo) {
// For logging
console.warn("React Error Boundary Catch", error, errorInfo)
console.warn("React Error Boundary Catch", error, errorInfo);
const { onError } = this.props;
if (onError) {
onError(error, errorInfo);
onError(error, errorInfo, null);
}
}
// 非同期例外対応
updateError() {
this.setState({ hasError: true });
}
handledRejection = (event: PromiseRejectionEvent) => {
const { onError } = this.props;
const error = new Error(event.type);
onError(error, null, event.reason);
this.setState({ hasError: true });
};
componentDidMount() {
window.addEventListener('unhandledrejection', this.eventHandler)
// window.addEventListener('unhandledrejection', this.eventHandler)
window.addEventListener("unhandledrejection", this.handledRejection);
}
componentWillUnmount() {
window.removeEventListener('unhandledrejection', this.eventHandler)
// window.removeEventListener('unhandledrejection', this.eventHandler)
window.removeEventListener("unhandledrejection", this.handledRejection);
}
render() {
@ -52,5 +60,4 @@ class ErrorBoundary extends React.Component<ErrorBoundaryProps, ErrorBoundarySta
}
}
export default ErrorBoundary;
export default ErrorBoundary;

View File

@ -0,0 +1,41 @@
export class BlockingQueue<T> {
private _promises: Promise<T>[];
private _resolvers: ((t: T) => void)[];
constructor() {
this._resolvers = [];
this._promises = [];
}
private _add() {
this._promises.push(
new Promise((resolve) => {
this._resolvers.push(resolve);
})
);
}
enqueue(t: T) {
if (this._resolvers.length == 0) this._add();
const resolve = this._resolvers.shift()!;
resolve(t);
}
dequeue() {
if (this._promises.length == 0) this._add();
const promise = this._promises.shift()!;
return promise;
}
isEmpty() {
return this._promises.length == 0;
}
isBlocked() {
return this._resolvers.length != 0;
}
get length() {
return this._promises.length - this._resolvers.length;
}
}

View File

@ -1,7 +1,8 @@
import React, { useContext, useEffect, useState } from "react";
import React, { useContext, useEffect, useState, useRef } from "react";
import { ReactNode } from "react";
import { useAppRoot } from "../../001_provider/001_AppRootProvider";
import { StateControlCheckbox, useStateControlCheckbox } from "../../hooks/useStateControlCheckbox";
import { useAppState } from "../../001_provider/001_AppStateProvider";
export const OpenServerControlCheckbox = "open-server-control-checkbox";
export const OpenModelSettingCheckbox = "open-model-setting-checkbox";
@ -20,8 +21,11 @@ export const OpenMergeLabDialogCheckbox = "open-merge-lab-dialog-checkbox";
export const OpenAdvancedSettingDialogCheckbox = "open-advanced-setting-dialog-checkbox";
export const OpenGetServerInformationDialogCheckbox = "open-get-server-information-dialog-checkbox";
export const OpenGetClientInformationDialogCheckbox = "open-get-client-information-dialog-checkbox";
export const OpenEnablePassThroughDialogCheckbox = "open-enable-pass-through-dialog-checkbox";
export const OpenTextInputDialogCheckbox = "open-text-input-dialog-checkbox";
export const OpenShowLicenseDialogCheckbox = "open-show-license-dialog-checkbox";
type Props = {
children: ReactNode;
};
@ -44,7 +48,9 @@ export type StateControls = {
showAdvancedSettingCheckbox: StateControlCheckbox;
showGetServerInformationCheckbox: StateControlCheckbox;
showGetClientInformationCheckbox: StateControlCheckbox;
showEnablePassThroughDialogCheckbox: StateControlCheckbox;
showTextInputCheckbox: StateControlCheckbox;
showLicenseCheckbox: StateControlCheckbox;
};
type GuiStateAndMethod = {
@ -56,17 +62,22 @@ type GuiStateAndMethod = {
setIsAnalyzing: (val: boolean) => void;
setShowPyTorchModelUpload: (val: boolean) => void;
reloadDeviceInfo: () => Promise<void>;
inputAudioDeviceInfo: MediaDeviceInfo[];
outputAudioDeviceInfo: MediaDeviceInfo[];
audioInputForGUI: string;
audioOutputForGUI: string;
audioMonitorForGUI: string;
fileInputEchoback: boolean | undefined;
shareScreenEnabled: boolean;
audioOutputForAnalyzer: string;
setInputAudioDeviceInfo: (val: MediaDeviceInfo[]) => void;
setOutputAudioDeviceInfo: (val: MediaDeviceInfo[]) => void;
setAudioInputForGUI: (val: string) => void;
setAudioOutputForGUI: (val: string) => void;
setAudioMonitorForGUI: (val: string) => void;
setFileInputEchoback: (val: boolean) => void;
setShareScreenEnabled: (val: boolean) => void;
setAudioOutputForAnalyzer: (val: string) => void;
modelSlotNum: number;
@ -74,6 +85,12 @@ type GuiStateAndMethod = {
textInputResolve: TextInputResolveType | null;
setTextInputResolve: (val: TextInputResolveType | null) => void;
// for Beatrice
beatriceJVSSpeakerId: number;
beatriceJVSSpeakerPitch: number;
setBeatriceJVSSpeakerId: (id: number) => void;
setBeatriceJVSSpeakerPitch: (pitch: number) => void;
};
const GuiStateContext = React.createContext<GuiStateAndMethod | null>(null);
@ -91,6 +108,7 @@ type TextInputResolveType = {
export const GuiStateProvider = ({ children }: Props) => {
const { appGuiSettingState } = useAppRoot();
const { serverSetting } = useAppState();
const [isConverting, setIsConverting] = useState<boolean>(false);
const [isAnalyzing, setIsAnalyzing] = useState<boolean>(false);
const [modelSlotNum, setModelSlotNum] = useState<number>(0);
@ -101,19 +119,30 @@ export const GuiStateProvider = ({ children }: Props) => {
const [outputAudioDeviceInfo, setOutputAudioDeviceInfo] = useState<MediaDeviceInfo[]>([]);
const [audioInputForGUI, setAudioInputForGUI] = useState<string>("none");
const [audioOutputForGUI, setAudioOutputForGUI] = useState<string>("none");
const [audioMonitorForGUI, setAudioMonitorForGUI] = useState<string>("none");
const [fileInputEchoback, setFileInputEchoback] = useState<boolean>(false); //最初のmuteが有効になるように。undefined <-- ??? falseしておけばよさそう。undefinedだとwarningがでる。
const [shareScreenEnabled, setShareScreenEnabled] = useState<boolean>(false);
const [audioOutputForAnalyzer, setAudioOutputForAnalyzer] = useState<string>("default");
const [textInputResolve, setTextInputResolve] = useState<TextInputResolveType | null>(null);
const reloadDeviceInfo = async () => {
try {
const ms = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
ms.getTracks().forEach((x) => {
x.stop();
});
} catch (e) {
console.warn("Enumerate device error::", e);
const [beatriceJVSSpeakerId, setBeatriceJVSSpeakerId] = useState<number>(1);
const [beatriceJVSSpeakerPitch, setBeatriceJVSSpeakerPitch] = useState<number>(0);
const checkDeviceAvailable = useRef<boolean>(false);
const _reloadDeviceInfo = async () => {
// デバイスチェックの空振り
if (checkDeviceAvailable.current == false) {
try {
const ms = await navigator.mediaDevices.getUserMedia({ video: false, audio: true });
ms.getTracks().forEach((x) => {
x.stop();
});
checkDeviceAvailable.current = true;
} catch (e) {
console.warn("Enumerate device error::", e);
}
}
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
@ -134,6 +163,13 @@ export const GuiStateProvider = ({ children }: Props) => {
label: "file",
toJSON: () => {},
});
audioInputs.push({
deviceId: "screen",
groupId: "screen",
kind: "audioinput",
label: "system(only win)",
toJSON: () => {},
});
const audioOutputs = mediaDeviceInfos.filter((x) => {
return x.kind == "audiooutput";
});
@ -153,14 +189,66 @@ export const GuiStateProvider = ({ children }: Props) => {
// })
return [audioInputs, audioOutputs];
};
const reloadDeviceInfo = async () => {
const audioInfo = await _reloadDeviceInfo();
setInputAudioDeviceInfo(audioInfo[0]);
setOutputAudioDeviceInfo(audioInfo[1]);
};
// useEffect(() => {
// const audioInitialize = async () => {
// await reloadDeviceInfo();
// };
// audioInitialize();
// }, []);
useEffect(() => {
const audioInitialize = async () => {
const audioInfo = await reloadDeviceInfo();
setInputAudioDeviceInfo(audioInfo[0]);
setOutputAudioDeviceInfo(audioInfo[1]);
let isMounted = true;
// デバイスのポーリングを再帰的に実行する関数
const pollDevices = async () => {
const checkDeviceDiff = (knownDeviceIds: Set<string>, newDeviceIds: Set<string>) => {
const deleted = new Set([...knownDeviceIds].filter((x) => !newDeviceIds.has(x)));
const added = new Set([...newDeviceIds].filter((x) => !knownDeviceIds.has(x)));
return { deleted, added };
};
try {
const audioInfo = await _reloadDeviceInfo();
const knownAudioinputIds = new Set(inputAudioDeviceInfo.map((x) => x.deviceId));
const newAudioinputIds = new Set(audioInfo[0].map((x) => x.deviceId));
const knownAudiooutputIds = new Set(outputAudioDeviceInfo.map((x) => x.deviceId));
const newAudiooutputIds = new Set(audioInfo[1].map((x) => x.deviceId));
const audioInputDiff = checkDeviceDiff(knownAudioinputIds, newAudioinputIds);
const audioOutputDiff = checkDeviceDiff(knownAudiooutputIds, newAudiooutputIds);
if (audioInputDiff.deleted.size > 0 || audioInputDiff.added.size > 0) {
console.log(`deleted input device: ${[...audioInputDiff.deleted]}`);
console.log(`added input device: ${[...audioInputDiff.added]}`);
setInputAudioDeviceInfo(audioInfo[0]);
}
if (audioOutputDiff.deleted.size > 0 || audioOutputDiff.added.size > 0) {
console.log(`deleted output device: ${[...audioOutputDiff.deleted]}`);
console.log(`added output device: ${[...audioOutputDiff.added]}`);
setOutputAudioDeviceInfo(audioInfo[1]);
}
if (isMounted) {
setTimeout(pollDevices, 1000 * 3);
}
} catch (err) {
console.error("An error occurred during enumeration of devices:", err);
}
};
audioInitialize();
}, []);
pollDevices();
return () => {
isMounted = false;
};
}, [inputAudioDeviceInfo, outputAudioDeviceInfo]);
// (1) Controller Switch
const openServerControlCheckbox = useStateControlCheckbox(OpenServerControlCheckbox);
@ -179,8 +267,10 @@ export const GuiStateProvider = ({ children }: Props) => {
const showAdvancedSettingCheckbox = useStateControlCheckbox(OpenAdvancedSettingDialogCheckbox);
const showGetServerInformationCheckbox = useStateControlCheckbox(OpenGetServerInformationDialogCheckbox);
const showGetClientInformationCheckbox = useStateControlCheckbox(OpenGetClientInformationDialogCheckbox);
const showEnablePassThroughDialogCheckbox = useStateControlCheckbox(OpenEnablePassThroughDialogCheckbox);
const showTextInputCheckbox = useStateControlCheckbox(OpenTextInputDialogCheckbox);
const showLicenseCheckbox = useStateControlCheckbox(OpenShowLicenseDialogCheckbox);
useEffect(() => {
openServerControlCheckbox.updateState(true);
@ -200,8 +290,10 @@ export const GuiStateProvider = ({ children }: Props) => {
showAdvancedSettingCheckbox.updateState(false);
showGetServerInformationCheckbox.updateState(false);
showGetClientInformationCheckbox.updateState(false);
showEnablePassThroughDialogCheckbox.updateState(false);
showTextInputCheckbox.updateState(false);
showLicenseCheckbox.updateState(false);
}, []);
useEffect(() => {
@ -220,7 +312,25 @@ export const GuiStateProvider = ({ children }: Props) => {
setTimeout(show);
}, [appGuiSettingState.edition]);
const providerValue = {
useEffect(() => {
let dstId;
if (beatriceJVSSpeakerPitch == 0) {
dstId = (beatriceJVSSpeakerId - 1) * 5;
} else if (beatriceJVSSpeakerPitch == 1) {
dstId = (beatriceJVSSpeakerId - 1) * 5 + 1;
} else if (beatriceJVSSpeakerPitch == 2) {
dstId = (beatriceJVSSpeakerId - 1) * 5 + 2;
} else if (beatriceJVSSpeakerPitch == -1) {
dstId = (beatriceJVSSpeakerId - 1) * 5 + 3;
} else if (beatriceJVSSpeakerPitch == -2) {
dstId = (beatriceJVSSpeakerId - 1) * 5 + 4;
} else {
throw new Error(`invalid beatriceJVSSpeakerPitch speaker:${beatriceJVSSpeakerId} pitch:${beatriceJVSSpeakerPitch}`);
}
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, dstId: dstId });
}, [beatriceJVSSpeakerId, beatriceJVSSpeakerPitch]);
const providerValue: GuiStateAndMethod = {
stateControls: {
openServerControlCheckbox,
openModelSettingCheckbox,
@ -239,8 +349,10 @@ export const GuiStateProvider = ({ children }: Props) => {
showAdvancedSettingCheckbox,
showGetServerInformationCheckbox,
showGetClientInformationCheckbox,
showEnablePassThroughDialogCheckbox,
showTextInputCheckbox,
showLicenseCheckbox,
},
isConverting,
setIsConverting,
@ -254,13 +366,17 @@ export const GuiStateProvider = ({ children }: Props) => {
outputAudioDeviceInfo,
audioInputForGUI,
audioOutputForGUI,
audioMonitorForGUI,
fileInputEchoback,
shareScreenEnabled,
audioOutputForAnalyzer,
setInputAudioDeviceInfo,
setOutputAudioDeviceInfo,
setAudioInputForGUI,
setAudioOutputForGUI,
setAudioMonitorForGUI,
setFileInputEchoback,
setShareScreenEnabled,
setAudioOutputForAnalyzer,
modelSlotNum,
@ -268,6 +384,12 @@ export const GuiStateProvider = ({ children }: Props) => {
textInputResolve,
setTextInputResolve,
// For Beatrice
beatriceJVSSpeakerId,
beatriceJVSSpeakerPitch,
setBeatriceJVSSpeakerId,
setBeatriceJVSSpeakerPitch,
};
return <GuiStateContext.Provider value={providerValue}>{children}</GuiStateContext.Provider>;
};

View File

@ -1,4 +1,4 @@
import React from "react"
import React from "react";
import { GuiStateProvider } from "./001_GuiStateProvider";
import { Dialogs } from "./900_Dialogs";
import { ModelSlotControl } from "./b00_ModelSlotControl";
@ -13,5 +13,5 @@ export const Demo = () => {
<ModelSlotControl></ModelSlotControl>
</div>
</GuiStateProvider>
)
}
);
};

View File

@ -7,6 +7,7 @@ import { MergeLabDialog } from "./905_MergeLabDialog";
import { AdvancedSettingDialog } from "./906_AdvancedSettingDialog";
import { GetServerInfomationDialog } from "./907_GetServerInfomationDialog";
import { GetClientInfomationDialog } from "./908_GetClientInfomationDialog";
import { EnablePassThroughDialog } from "./909_EnablePassThroughDialog";
export const Dialogs = () => {
const guiState = useGuiState();
@ -19,6 +20,7 @@ export const Dialogs = () => {
{guiState.stateControls.showAdvancedSettingCheckbox.trigger}
{guiState.stateControls.showGetServerInformationCheckbox.trigger}
{guiState.stateControls.showGetClientInformationCheckbox.trigger}
{guiState.stateControls.showEnablePassThroughDialogCheckbox.trigger}
<div className="dialog-container" id="dialog">
{guiState.stateControls.showWaitingCheckbox.trigger}
<WaitingDialog></WaitingDialog>
@ -34,6 +36,8 @@ export const Dialogs = () => {
<GetServerInfomationDialog></GetServerInfomationDialog>
{guiState.stateControls.showGetClientInformationCheckbox.trigger}
<GetClientInfomationDialog></GetClientInfomationDialog>
{guiState.stateControls.showEnablePassThroughDialogCheckbox.trigger}
<EnablePassThroughDialog></EnablePassThroughDialog>
</div>
</div>
);

View File

@ -4,106 +4,167 @@ import { isDesktopApp } from "../../const";
import { useAppRoot } from "../../001_provider/001_AppRootProvider";
import { useMessageBuilder } from "../../hooks/useMessageBuilder";
export const StartingNoticeDialog = () => {
const guiState = useGuiState()
const { appGuiSettingState } = useAppRoot()
const guiState = useGuiState();
const { appGuiSettingState } = useAppRoot();
const messageBuilderState = useMessageBuilder()
const messageBuilderState = useMessageBuilder();
useMemo(() => {
messageBuilderState.setMessage(__filename, "support", { "ja": "支援", "en": "Donation" })
messageBuilderState.setMessage(__filename, "support_message_1", { "ja": "このソフトウェアを気に入ったら開発者にコーヒーをご馳走してあげよう。黄色いアイコンから。", "en": "This software is supported by donations. Thank you for your support!" })
messageBuilderState.setMessage(__filename, "support_message_2", { "ja": "コーヒーをご馳走する。", "en": "I will support a developer by buying coffee." })
messageBuilderState.setMessage(__filename, "directml_1", { "ja": "directML版は実験的バージョンです。以下の既知の問題があります。", "en": "DirectML version is an experimental version. There are the known issues as follows." })
messageBuilderState.setMessage(__filename, "directml_2", { "ja": "(1) 一部の設定変更を行うとgpuを使用していても変換処理が遅くなることが発生します。もしこの現象が発生したらGPUの値を-1にしてから再度0に戻してください。", "en": "(1) When some settings are changed, conversion process becomes slow even when using GPU. If this occurs, reset the GPU value to -1 and then back to 0." })
messageBuilderState.setMessage(__filename, "click_to_start", { "ja": "スタートボタンを押してください。", "en": "Click to start" })
messageBuilderState.setMessage(__filename, "start", { "ja": "スタート", "en": "start" })
}, [])
messageBuilderState.setMessage(__filename, "support", { ja: "支援", en: "Donation" });
messageBuilderState.setMessage(__filename, "support_message_1", { ja: "このソフトウェアを気に入ったら開発者にコーヒーをご馳走してあげよう。黄色いアイコンから。", en: "This software is supported by donations. Thank you for your support!" });
messageBuilderState.setMessage(__filename, "support_message_2", { ja: "コーヒーをご馳走する。", en: "I will support a developer by buying coffee." });
messageBuilderState.setMessage(__filename, "directml_1", { ja: "directML版は実験的バージョンです。以下の既知の問題があります。", en: "DirectML version is an experimental version. There are the known issues as follows." });
messageBuilderState.setMessage(__filename, "directml_2", {
ja: "(1) 一部の設定変更を行うとgpuを使用していても変換処理が遅くなることが発生します。もしこの現象が発生したらGPUの値を-1にしてから再度0に戻してください。",
en: "(1) When some settings are changed, conversion process becomes slow even when using GPU. If this occurs, reset the GPU value to -1 and then back to 0.",
});
messageBuilderState.setMessage(__filename, "web_edditon_1", { ja: "このWebエディションは実験的バージョンです。", en: "This edition(web) is an experimental Edition." });
messageBuilderState.setMessage(__filename, "web_edditon_2", {
ja: "より高機能・高性能なFullエディションは、",
en: "The more advanced and high-performance Full Edition can be obtained for free from the following GitHub repository.",
});
messageBuilderState.setMessage(__filename, "web_edditon_3", {
ja: "次のgithubリポジトリから無料で取得できます。",
en: "",
});
messageBuilderState.setMessage(__filename, "github", { ja: "github", en: "github" });
messageBuilderState.setMessage(__filename, "click_to_start", { ja: "スタートボタンを押してください。", en: "Click to start" });
messageBuilderState.setMessage(__filename, "start", { ja: "スタート", en: "start" });
}, []);
const coffeeLink = useMemo(() => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link" onClick={() => { window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad") }}>
<img className="donate-img" src="./assets/buymeacoffee.png" /> {messageBuilderState.getMessage(__filename, "support_message_2")}
</span>
)
:
(
<a className="link" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" /> {messageBuilderState.getMessage(__filename, "support_message_2")}
</a>
)
}, [])
return isDesktopApp() ? (
// @ts-ignore
<span
className="link"
onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad");
}}
>
<img className="donate-img" src="./assets/buymeacoffee.png" /> {messageBuilderState.getMessage(__filename, "support_message_2")}
</span>
) : (
<a className="link" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" /> {messageBuilderState.getMessage(__filename, "support_message_2")}
</a>
);
}, []);
const licenseNoticeLink = useMemo(() => {
return isDesktopApp() ? (
<span
className="link"
onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer/blob/master/LICENSE-NOTICE");
}}
>
License Notice
</span>
) : (
<a className="link" href="https://github.com/w-okada/voice-changer/blob/master/LICENSE-NOTICE" target="_blank" rel="noopener noreferrer">
License Notice
</a>
);
}, []);
const dialog = useMemo(() => {
const closeButtonRow = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-item-text">
</div>
<div className="body-item-text"></div>
<div className="body-button-container body-button-container-space-around">
<div className="body-button" onClick={() => { guiState.stateControls.showStartingNoticeCheckbox.updateState(false) }} >
<div
className="body-button"
onClick={() => {
guiState.stateControls.showStartingNoticeCheckbox.updateState(false);
}}
>
{messageBuilderState.getMessage(__filename, "start")}
</div>
</div>
<div className="body-item-text"></div>
</div>
)
);
const donationMessage = (
<div className="dialog-content-part">
<div>
{messageBuilderState.getMessage(__filename, "support_message_1")}
</div>
<div>
{coffeeLink}
</div>
<div>{messageBuilderState.getMessage(__filename, "support_message_1")}</div>
<div>{coffeeLink}</div>
</div>
)
);
const directMLMessage = (
<div className="dialog-content-part">
<div>
{messageBuilderState.getMessage(__filename, "directml_1")}
</div>
<div className="left-padding-1">
{messageBuilderState.getMessage(__filename, "directml_2")}
</div>
<div>{messageBuilderState.getMessage(__filename, "directml_1")}</div>
<div className="left-padding-1">{messageBuilderState.getMessage(__filename, "directml_2")}</div>
</div>
)
);
const licenseInfo = <div className="dialog-content-part">{licenseNoticeLink}</div>;
const webEdtionMessage = (
<div className="dialog-content-part">
<div>{messageBuilderState.getMessage(__filename, "web_edditon_1")}</div>
<div>{messageBuilderState.getMessage(__filename, "web_edditon_2")}</div>
<div>{messageBuilderState.getMessage(__filename, "web_edditon_3")}</div>
</div>
);
const githubLink = isDesktopApp() ? (
<span
className="link tooltip"
onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer");
}}
>
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
<div>github</div>
</span>
) : (
<a className="link tooltip" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/github.svg" />
<span>github</span>
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</a>
);
const clickToStartMessage = (
<div className="dialog-content-part">
<div>
{messageBuilderState.getMessage(__filename, "click_to_start")}
</div>
<div>{messageBuilderState.getMessage(__filename, "click_to_start")}</div>
</div>
)
const edition = appGuiSettingState.edition
);
const edition = appGuiSettingState.edition;
const content = (
<div className="body-row">
{donationMessage}
{edition.indexOf("onnxdirectML-cuda") >= 0 ? directMLMessage : <></>}
{licenseInfo}
{clickToStartMessage}
</div>
)
);
const contentForWeb = (
<div className="body-row">
{webEdtionMessage}
{githubLink}
{clickToStartMessage}
</div>
);
return (
<div className="dialog-frame">
<div className="dialog-title">Message</div>
<div className="dialog-content">
{content}
{edition.indexOf("web") >= 0 ? contentForWeb : content}
{closeButtonRow}
</div>
</div>
);
}, [appGuiSettingState.edition]);
return dialog;
};

View File

@ -1,156 +1,173 @@
import React, { useMemo } from "react";
import { useGuiState } from "./001_GuiStateProvider";
import { useAppState } from "../../001_provider/001_AppStateProvider";
import { DDSPSVCModelSlot, MMVCv13ModelSlot, MMVCv15ModelSlot, RVCModelSlot, SoVitsSvc40ModelSlot, fileSelector } from "@dannadori/voice-changer-client-js";
import { DDSPSVCModelSlot, DiffusionSVCModelSlot, MMVCv13ModelSlot, MMVCv15ModelSlot, RVCModelSlot, SoVitsSvc40ModelSlot, fileSelector } from "@dannadori/voice-changer-client-js";
import { useMessageBuilder } from "../../hooks/useMessageBuilder";
import { ModelSlotManagerDialogScreen } from "./904_ModelSlotManagerDialog";
import { checkExtention, trimfileName } from "../../utils/utils";
import { MODEL_ICON_BLANK_URL } from "../../const";
export type MainScreenProps = {
screen: ModelSlotManagerDialogScreen
close: () => void
openSampleDownloader: (slotIndex: number) => void
openFileUploader: (slotIndex: number) => void
openEditor: (slotIndex: number) => void
}
screen: ModelSlotManagerDialogScreen;
close: () => void;
openSampleDownloader: (slotIndex: number) => void;
openFileUploader: (slotIndex: number) => void;
openEditor: (slotIndex: number) => void;
};
export const MainScreen = (props: MainScreenProps) => {
const { serverSetting } = useAppState()
const guiState = useGuiState()
const messageBuilderState = useMessageBuilder()
const { serverSetting } = useAppState();
const guiState = useGuiState();
const messageBuilderState = useMessageBuilder();
useMemo(() => {
messageBuilderState.setMessage(__filename, "change_icon", { "ja": "アイコン変更", "en": "chage icon" })
messageBuilderState.setMessage(__filename, "rename", { "ja": "リネーム", "en": "rename" })
messageBuilderState.setMessage(__filename, "download", { "ja": "ダウンロード", "en": "download" })
messageBuilderState.setMessage(__filename, "terms_of_use", { "ja": "利用規約", "en": "terms of use" })
messageBuilderState.setMessage(__filename, "sample", { "ja": "サンプル", "en": "DL sample" })
messageBuilderState.setMessage(__filename, "upload", { "ja": "アップロード", "en": "upload" })
messageBuilderState.setMessage(__filename, "edit", { "ja": "編集", "en": "edit" })
messageBuilderState.setMessage(__filename, "close", { "ja": "閉じる", "en": "close" })
}, [])
messageBuilderState.setMessage(__filename, "change_icon", { ja: "アイコン変更", en: "change icon" });
messageBuilderState.setMessage(__filename, "rename", { ja: "リネーム", en: "rename" });
messageBuilderState.setMessage(__filename, "download", { ja: "ダウンロード", en: "download" });
messageBuilderState.setMessage(__filename, "terms_of_use", { ja: "利用規約", en: "terms of use" });
messageBuilderState.setMessage(__filename, "sample", { ja: "サンプル", en: "DL sample" });
messageBuilderState.setMessage(__filename, "upload", { ja: "アップロード", en: "upload" });
messageBuilderState.setMessage(__filename, "edit", { ja: "編集", en: "edit" });
messageBuilderState.setMessage(__filename, "close", { ja: "閉じる", en: "close" });
}, []);
const screen = useMemo(() => {
if (props.screen != "Main") {
return <></>
return <></>;
}
if (!serverSetting.serverSetting.modelSlots) {
return <></>
return <></>;
}
const iconAction = async (index: number) => {
if (!serverSetting.serverSetting.modelSlots[index].name || serverSetting.serverSetting.modelSlots[index].name.length == 0) {
return
return;
}
const file = await fileSelector("")
const file = await fileSelector("");
if (checkExtention(file.name, ["png", "jpg", "jpeg", "gif"]) == false) {
alert(`サムネイルの拡張子は".png", ".jpg", ".jpeg", ".gif"である必要があります。`)
return
alert(`サムネイルの拡張子は".png", ".jpg", ".jpeg", ".gif"である必要があります。`);
return;
}
await serverSetting.uploadAssets(index, "iconFile", file)
}
await serverSetting.uploadAssets(index, "iconFile", file);
};
const nameValueAction = async (index: number) => {
if (!serverSetting.serverSetting.modelSlots[index].name || serverSetting.serverSetting.modelSlots[index].name.length == 0) {
return
return;
}
// Open Text Input Dialog
const p = new Promise<string>((resolve) => {
guiState.setTextInputResolve({ resolve: resolve })
})
guiState.stateControls.showTextInputCheckbox.updateState(true)
guiState.setTextInputResolve({ resolve: resolve });
});
guiState.stateControls.showTextInputCheckbox.updateState(true);
const text = await p;
// Send to Server
if (text.length > 0) {
console.log("input text:", text)
await serverSetting.updateModelInfo(index, "name", text)
console.log("input text:", text);
await serverSetting.updateModelInfo(index, "name", text);
}
}
};
const fileValueAction = (url: string) => {
if (url.length == 0) {
return
return;
}
const link = document.createElement("a")
link.href = "./" + url
link.download = url.replace(/^.*[\\\/]/, '')
link.click()
link.remove()
}
const link = document.createElement("a");
link.href = "./" + url;
link.download = url.replace(/^.*[\\\/]/, "");
link.click();
link.remove();
};
const closeButtonRow = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-item-text">
</div>
<div className="body-item-text"></div>
<div className="body-button-container body-button-container-space-around">
<div className="body-button" onClick={() => { props.close() }} >
<div
className="body-button"
onClick={() => {
props.close();
}}
>
{messageBuilderState.getMessage(__filename, "close")}
</div>
</div>
<div className="body-item-text"></div>
</div>
)
);
const slotRow = serverSetting.serverSetting.modelSlots.map((x, index) => {
// モデルのアイコン
const generateIconArea = (slotIndex: number, iconUrl: string, tooltip: boolean) => {
const realIconUrl = iconUrl.length > 0 ? iconUrl : "/assets/icons/noimage.png"
const iconDivClass = tooltip ? "tooltip" : ""
const iconClass = tooltip ? "model-slot-icon-pointable" : "model-slot-icon"
const generateIconArea = (slotIndex: number, iconUrl: string | null, tooltip: boolean) => {
let realIconUrl = MODEL_ICON_BLANK_URL;
if (iconUrl) {
realIconUrl = iconUrl.length > 0 ? serverSetting.serverSetting.voiceChangerParams.model_dir + "/" + slotIndex + "/" + iconUrl.split(/[\/\\]/).pop() : "/assets/icons/noimage.png";
}
const iconDivClass = tooltip ? "tooltip" : "";
const iconClass = tooltip ? "model-slot-icon-pointable" : "model-slot-icon";
return (
<div className={iconDivClass}>
<img src={realIconUrl} className={iconClass} onClick={() => { iconAction(slotIndex) }} />
<div className="tooltip-text tooltip-text-thin tooltip-text-lower">
{messageBuilderState.getMessage(__filename, "change_icon")}
</div>
<img
src={realIconUrl}
className={iconClass}
onClick={() => {
iconAction(slotIndex);
}}
/>
<div className="tooltip-text tooltip-text-thin tooltip-text-lower">{messageBuilderState.getMessage(__filename, "change_icon")}</div>
</div>
)
}
);
};
// モデルの名前
const generateNameRow = (slotIndex: number, name: string, termsOfUseUrl: string) => {
const nameValueClass = name.length > 0 ? "model-slot-detail-row-value-pointable tooltip" : "model-slot-detail-row-value"
const displayName = name.length > 0 ? name : "blank"
const termOfUseUrlLink = termsOfUseUrl.length > 0 ? <a href={termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[{messageBuilderState.getMessage(__filename, "terms_of_use")}]</a> : <></>
const nameValueClass = name.length > 0 ? "model-slot-detail-row-value-pointable tooltip" : "model-slot-detail-row-value";
const displayName = name.length > 0 ? name : "blank";
const termOfUseUrlLink =
termsOfUseUrl.length > 0 ? (
<a href={termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">
[{messageBuilderState.getMessage(__filename, "terms_of_use")}]
</a>
) : (
<></>
);
return (
<div className="model-slot-detail-row">
<div className="model-slot-detail-row-label">[{slotIndex}]</div>
<div className={nameValueClass} onClick={() => { nameValueAction(slotIndex) }}>
<div
className={nameValueClass}
onClick={() => {
nameValueAction(slotIndex);
}}
>
{displayName}
<div className="tooltip-text tooltip-text-thin">
{messageBuilderState.getMessage(__filename, "rename")}
</div>
<div className="tooltip-text tooltip-text-thin">{messageBuilderState.getMessage(__filename, "rename")}</div>
</div>
<div className="">{termOfUseUrlLink}</div>
</div>
)
}
);
};
// モデルを構成するファイル
const generateFileRow = (title: string, filePath: string) => {
const fileValueClass = filePath.length > 0 ? "model-slot-detail-row-value-download tooltip" : "model-slot-detail-row-value"
const fileValueClass = filePath.length > 0 ? "model-slot-detail-row-value-download tooltip" : "model-slot-detail-row-value";
return (
<div key={`${title}`} className="model-slot-detail-row">
<div className="model-slot-detail-row-label">{title}:</div>
<div className={fileValueClass} onClick={() => { fileValueAction(filePath) }}>
<div
className={fileValueClass}
onClick={() => {
fileValueAction(filePath);
}}
>
{trimfileName(filePath, 20)}
<div className="tooltip-text tooltip-text-thin">
{messageBuilderState.getMessage(__filename, "download")}
</div>
<div className="tooltip-text tooltip-text-thin">{messageBuilderState.getMessage(__filename, "download")}</div>
</div>
</div>
)
}
);
};
// その他情報欄
const generateInfoRow = (info: string) => {
@ -160,55 +177,60 @@ export const MainScreen = (props: MainScreenProps) => {
<div className="model-slot-detail-row-value">{info}</div>
<div className=""></div>
</div>
)
}
);
};
let iconArea = <></>
let nameRow = <></>
const fileRows = []
let infoRow = <></>
let iconArea = <></>;
let nameRow = <></>;
const fileRows = [];
let infoRow = <></>;
if (x.voiceChangerType == "RVC") {
const slotInfo = x as RVCModelSlot
iconArea = generateIconArea(index, slotInfo.iconFile, true)
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl)
fileRows.push(generateFileRow("model", slotInfo.modelFile))
fileRows.push(generateFileRow("index", slotInfo.indexFile))
infoRow = generateInfoRow(`${slotInfo.f0 ? "f0" : "nof0"}, ${slotInfo.samplingRate}, ${slotInfo.embChannels}, ${slotInfo.modelType}, ${slotInfo.defaultTune}, ${slotInfo.defaultIndexRatio}, ${slotInfo.defaultProtect}`)
const slotInfo = x as RVCModelSlot;
iconArea = generateIconArea(index, slotInfo.iconFile, true);
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl);
fileRows.push(generateFileRow("model", slotInfo.modelFile));
fileRows.push(generateFileRow("index", slotInfo.indexFile));
infoRow = generateInfoRow(`${slotInfo.f0 ? "f0" : "nof0"}, ${slotInfo.samplingRate}, ${slotInfo.embChannels}, ${slotInfo.modelType}, ${slotInfo.defaultTune}, ${slotInfo.defaultIndexRatio}, ${slotInfo.defaultProtect}`);
} else if (x.voiceChangerType == "MMVCv13") {
const slotInfo = x as MMVCv13ModelSlot
iconArea = generateIconArea(index, slotInfo.iconFile, true)
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl)
fileRows.push(generateFileRow("config", slotInfo.configFile))
fileRows.push(generateFileRow("model", slotInfo.modelFile))
infoRow = generateInfoRow(``)
const slotInfo = x as MMVCv13ModelSlot;
iconArea = generateIconArea(index, slotInfo.iconFile, true);
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl);
fileRows.push(generateFileRow("config", slotInfo.configFile));
fileRows.push(generateFileRow("model", slotInfo.modelFile));
infoRow = generateInfoRow(``);
} else if (x.voiceChangerType == "MMVCv15") {
const slotInfo = x as MMVCv15ModelSlot
iconArea = generateIconArea(index, slotInfo.iconFile, true)
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl)
fileRows.push(generateFileRow("config", slotInfo.configFile))
fileRows.push(generateFileRow("model", slotInfo.modelFile))
infoRow = generateInfoRow((`f0factor:${slotInfo.f0Factor}`))
const slotInfo = x as MMVCv15ModelSlot;
iconArea = generateIconArea(index, slotInfo.iconFile, true);
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl);
fileRows.push(generateFileRow("config", slotInfo.configFile));
fileRows.push(generateFileRow("model", slotInfo.modelFile));
infoRow = generateInfoRow(`f0factor:${slotInfo.f0Factor}`);
} else if (x.voiceChangerType == "so-vits-svc-40") {
const slotInfo = x as SoVitsSvc40ModelSlot
iconArea = generateIconArea(index, slotInfo.iconFile, true)
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl)
fileRows.push(generateFileRow("config", slotInfo.configFile))
fileRows.push(generateFileRow("model", slotInfo.modelFile))
fileRows.push(generateFileRow("cluster", slotInfo.clusterFile))
infoRow = generateInfoRow((`tune:${slotInfo.defaultTune},cluster:${slotInfo.defaultClusterInferRatio},noise:${slotInfo.noiseScale}`))
const slotInfo = x as SoVitsSvc40ModelSlot;
iconArea = generateIconArea(index, slotInfo.iconFile, true);
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl);
fileRows.push(generateFileRow("config", slotInfo.configFile));
fileRows.push(generateFileRow("model", slotInfo.modelFile));
fileRows.push(generateFileRow("cluster", slotInfo.clusterFile));
infoRow = generateInfoRow(`tune:${slotInfo.defaultTune},cluster:${slotInfo.defaultClusterInferRatio},noise:${slotInfo.noiseScale}`);
} else if (x.voiceChangerType == "DDSP-SVC") {
const slotInfo = x as DDSPSVCModelSlot
iconArea = generateIconArea(index, slotInfo.iconFile, true)
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl)
fileRows.push(generateFileRow("config", slotInfo.configFile))
fileRows.push(generateFileRow("model", slotInfo.modelFile))
fileRows.push(generateFileRow("diff conf", slotInfo.diffConfigFile))
fileRows.push(generateFileRow("diff model", slotInfo.diffModelFile))
infoRow = generateInfoRow((`tune:${slotInfo.defaultTune},acc:${slotInfo.acc},ks:${slotInfo.kstep}, diff:${slotInfo.diffusion},enh:${slotInfo.enhancer}`))
const slotInfo = x as DDSPSVCModelSlot;
iconArea = generateIconArea(index, slotInfo.iconFile, true);
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl);
fileRows.push(generateFileRow("config", slotInfo.configFile));
fileRows.push(generateFileRow("model", slotInfo.modelFile));
fileRows.push(generateFileRow("diff conf", slotInfo.diffConfigFile));
fileRows.push(generateFileRow("diff model", slotInfo.diffModelFile));
infoRow = generateInfoRow(`tune:${slotInfo.defaultTune},acc:${slotInfo.acc},ks:${slotInfo.kstep}, diff:${slotInfo.diffusion},enh:${slotInfo.enhancer}`);
} else if (x.voiceChangerType == "Diffusion-SVC") {
const slotInfo = x as DiffusionSVCModelSlot;
iconArea = generateIconArea(index, slotInfo.iconFile, true);
nameRow = generateNameRow(index, slotInfo.name, slotInfo.termsOfUseUrl);
fileRows.push(generateFileRow("model", slotInfo.modelFile));
infoRow = generateInfoRow(`tune:${slotInfo.defaultTune},mks:${slotInfo.kStepMax},ks:${slotInfo.defaultKstep}, sp:${slotInfo.defaultSpeedup}, l:${slotInfo.nLayers},${slotInfo.nnLayers},`);
} else {
iconArea = generateIconArea(index, "/assets/icons/blank.png", false)
nameRow = generateNameRow(index, "", "")
iconArea = generateIconArea(index, null, false);
nameRow = generateNameRow(index, "", "");
}
return (
<div key={index} className="model-slot">
@ -219,26 +241,45 @@ export const MainScreen = (props: MainScreenProps) => {
{infoRow}
</div>
<div className="model-slot-buttons">
<div className="model-slot-button" onClick={() => { props.openFileUploader(index) }} >{messageBuilderState.getMessage(__filename, "upload")}</div>
<div className="model-slot-button" onClick={() => { props.openSampleDownloader(index) }} >{messageBuilderState.getMessage(__filename, "sample")}</div>
<div className="model-slot-button" onClick={() => { props.openEditor(index) }} >{messageBuilderState.getMessage(__filename, "edit")}</div>
<div
className="model-slot-button"
onClick={() => {
props.openFileUploader(index);
}}
>
{messageBuilderState.getMessage(__filename, "upload")}
</div>
<div
className="model-slot-button"
onClick={() => {
props.openSampleDownloader(index);
}}
>
{messageBuilderState.getMessage(__filename, "sample")}
</div>
<div
className="model-slot-button"
onClick={() => {
props.openEditor(index);
}}
>
{messageBuilderState.getMessage(__filename, "edit")}
</div>
</div>
</div >
)
})
</div>
);
});
return (
<div className="dialog-frame">
<div className="dialog-title">Model Slot Configuration</div>
<div className="dialog-fixed-size-content">
<div className="model-slot-container">
{slotRow}
</div>
<div className="model-slot-container">{slotRow}</div>
{closeButtonRow}
</div>
</div>
)
}, [props.screen, serverSetting.serverSetting])
);
}, [props.screen, serverSetting.serverSetting]);
return screen
}
return screen;
};

View File

@ -1,78 +1,94 @@
import React, { useMemo, useState } from "react";
import { useAppState } from "../../001_provider/001_AppStateProvider";
import { ModelUploadSetting } from "@dannadori/voice-changer-client-js";
import { DiffusionSVCSampleModel, ModelUploadSetting, RVCSampleModel, VoiceChangerType } from "@dannadori/voice-changer-client-js";
import { useMessageBuilder } from "../../hooks/useMessageBuilder";
import { ModelSlotManagerDialogScreen } from "./904_ModelSlotManagerDialog";
export type SampleDownloaderScreenProps = {
screen: ModelSlotManagerDialogScreen
targetIndex: number
close: () => void
backToSlotManager: () => void
}
screen: ModelSlotManagerDialogScreen;
targetIndex: number;
close: () => void;
backToSlotManager: () => void;
};
export const SampleDownloaderScreen = (props: SampleDownloaderScreenProps) => {
const { serverSetting } = useAppState()
const [lang, setLang] = useState<string>("All")
const messageBuilderState = useMessageBuilder()
const { serverSetting } = useAppState();
const [lang, setLang] = useState<string>("All");
const messageBuilderState = useMessageBuilder();
useMemo(() => {
messageBuilderState.setMessage(__filename, "header_message", { "ja": "サンプルをダウンロードしてください. 対象:", "en": "Download Sample for" })
messageBuilderState.setMessage(__filename, "lang", { "ja": "言語", "en": "Lang" })
messageBuilderState.setMessage(__filename, "back", { "ja": "戻る", "en": "back" })
messageBuilderState.setMessage(__filename, "terms_of_use", { "ja": "利用規約", "en": "terms of use" })
messageBuilderState.setMessage(__filename, "download", { "ja": "ダウンロード", "en": "download" })
}, [])
messageBuilderState.setMessage(__filename, "header_message", { ja: "サンプルをダウンロードしてください. 対象:", en: "Download Sample for" });
messageBuilderState.setMessage(__filename, "lang", { ja: "言語", en: "Lang" });
messageBuilderState.setMessage(__filename, "back", { ja: "戻る", en: "back" });
messageBuilderState.setMessage(__filename, "terms_of_use", { ja: "利用規約", en: "terms of use" });
messageBuilderState.setMessage(__filename, "download", { ja: "ダウンロード", en: "download" });
}, []);
/////////////////////////////////////////
// Sample Downloader
/////////////////////////////////////////
const screen = useMemo(() => {
if (props.screen != "SampleDownloader") {
return <></>
return <></>;
}
if (!serverSetting.serverSetting.modelSlots) {
return <></>
return <></>;
}
const langs = serverSetting.serverSetting.sampleModels.reduce((prev, cur) => {
if (prev.includes(cur.lang) == false) {
prev.push(cur.lang)
}
return prev
}, ["All"] as string[])
const langOptions = (
langs.map(x => {
return <option key={x} value={x}>{x}</option>
})
)
const onDownloadSampleClicked = async (id: string) => {
const langs = serverSetting.serverSetting.sampleModels.reduce(
(prev, cur) => {
if (prev.includes(cur.lang) == false) {
prev.push(cur.lang);
}
return prev;
},
["All"] as string[],
);
const langOptions = langs.map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
});
const onDownloadSampleClicked = async (id: string, voiceChangerType: VoiceChangerType, params: any) => {
const uploadParams: ModelUploadSetting = {
voiceChangerType: "RVC",
voiceChangerType: voiceChangerType,
slot: props.targetIndex,
isSampleMode: true,
sampleId: id,
files: [],
params: {
rvcIndexDownload: true
}
}
params: params,
};
try {
await serverSetting.uploadModel(uploadParams)
await serverSetting.uploadModel(uploadParams);
} catch (e) {
alert(e)
alert(e);
}
props.backToSlotManager()
props.backToSlotManager();
// setMode("localFile")
}
const options = (
serverSetting.serverSetting.sampleModels.filter(x => { return lang == "All" ? true : x.lang == lang }).map((x, index) => {
const termOfUseUrlLink = x.termsOfUseUrl && x.termsOfUseUrl.length > 0 ? <a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">[{messageBuilderState.getMessage(__filename, "terms_of_use")}]</a> : <></>
};
const options = serverSetting.serverSetting.sampleModels
.filter((x) => {
return lang == "All" ? true : x.lang == lang;
})
.map((x, index) => {
const termOfUseUrlLink =
x.termsOfUseUrl && x.termsOfUseUrl.length > 0 ? (
<a href={x.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="body-item-text-small">
[{messageBuilderState.getMessage(__filename, "terms_of_use")}]
</a>
) : (
<></>
);
let info = ``;
if (x.voiceChangerType == "RVC") {
const sampleInfo = x as RVCSampleModel;
info = `type:${sampleInfo.modelType}, f0:${sampleInfo.f0 ? "f0" : "nof0"}, sr:${sampleInfo.sampleRate}`;
} else if (x.voiceChangerType == "Diffusion-SVC") {
const sampleInfo = x as DiffusionSVCSampleModel;
info = `native_l:${sampleInfo.numOfNativeLayers}, diff_l:${sampleInfo.numOfDiffLayers}, max_kstep:${sampleInfo.maxKStep}`;
}
return (
<div key={index} className="model-slot">
<img src={x.icon} className="model-slot-icon"></img>
@ -82,21 +98,36 @@ export const SampleDownloaderScreen = (props: SampleDownloaderScreenProps) => {
<div className="model-slot-detail-row-value">{x.name}</div>
<div className="">{termOfUseUrlLink}</div>
</div>
<div className="model-slot-detail-row">
<div className="model-slot-detail-row-label">VC Type: </div>
<div className="model-slot-detail-row-value">{x.voiceChangerType}</div>
<div className=""></div>
</div>
<div className="model-slot-detail-row">
<div className="model-slot-detail-row-label">info: </div>
<div className="model-slot-detail-row-value">{x.modelType},{x.f0 ? "f0" : "nof0"},{x.sampleRate}</div>
<div className="model-slot-detail-row-value">{info}</div>
<div className=""></div>
</div>
</div>
<div className="model-slot-buttons">
<div className="model-slot-button" onClick={() => { onDownloadSampleClicked(x.id) }}>
<div
className="model-slot-button"
onClick={() => {
if (x.voiceChangerType == "RVC") {
onDownloadSampleClicked(x.id, x.voiceChangerType, {
useIndex: true,
});
} else if (x.voiceChangerType == "Diffusion-SVC") {
onDownloadSampleClicked(x.id, x.voiceChangerType, {});
}
}}
>
{messageBuilderState.getMessage(__filename, "download")}
</div>
</div>
</div>
)
})
)
);
});
return (
<div className="dialog-frame">
@ -104,32 +135,32 @@ export const SampleDownloaderScreen = (props: SampleDownloaderScreenProps) => {
<div className="dialog-fixed-size-content">
<div className="model-slot-header">
{messageBuilderState.getMessage(__filename, "header_message")} Slot[{props.targetIndex}]
<span onClick={() => { props.backToSlotManager() }} className="model-slot-header-button">
<span
onClick={() => {
props.backToSlotManager();
}}
className="model-slot-header-button"
>
&lt;&lt;{messageBuilderState.getMessage(__filename, "back")}
</span>
</div>
<div>{messageBuilderState.getMessage(__filename, "lang")}:
<select value={lang} onChange={(e) => { setLang(e.target.value) }}>
<div>
{messageBuilderState.getMessage(__filename, "lang")}:
<select
value={lang}
onChange={(e) => {
setLang(e.target.value);
}}
>
{langOptions}
</select>
</div>
<div className="model-slot-container">
{options}
</div>
<div className="model-slot-container">{options}</div>
</div>
</div>
)
}, [
props.screen,
props.targetIndex,
lang,
serverSetting.serverSetting
])
);
}, [props.screen, props.targetIndex, lang, serverSetting.serverSetting]);
return screen;
};

View File

@ -6,33 +6,33 @@ import { ModelSlotManagerDialogScreen } from "./904_ModelSlotManagerDialog";
import { checkExtention, trimfileName } from "../../utils/utils";
export type FileUploaderScreenProps = {
screen: ModelSlotManagerDialogScreen
targetIndex: number
close: () => void
backToSlotManager: () => void
}
screen: ModelSlotManagerDialogScreen;
targetIndex: number;
close: () => void;
backToSlotManager: () => void;
};
export const FileUploaderScreen = (props: FileUploaderScreenProps) => {
const { serverSetting } = useAppState()
const [voiceChangerType, setVoiceChangerType] = useState<VoiceChangerType>("RVC")
const [uploadSetting, setUploadSetting] = useState<ModelUploadSetting>()
const messageBuilderState = useMessageBuilder()
const { serverSetting } = useAppState();
const [voiceChangerType, setVoiceChangerType] = useState<VoiceChangerType>("RVC");
const [uploadSetting, setUploadSetting] = useState<ModelUploadSetting>();
const messageBuilderState = useMessageBuilder();
useMemo(() => {
messageBuilderState.setMessage(__filename, "header_message", { "ja": "ファイルをアップロードしてください. 対象:", "en": "Upload Files for " })
messageBuilderState.setMessage(__filename, "back", { "ja": "戻る", "en": "back" })
messageBuilderState.setMessage(__filename, "select", { "ja": "ファイル選択", "en": "select file" })
messageBuilderState.setMessage(__filename, "upload", { "ja": "アップロード", "en": "upload" })
messageBuilderState.setMessage(__filename, "uploading", { "ja": "アップロード中", "en": "uploading" })
messageBuilderState.setMessage(__filename, "header_message", { ja: "ファイルをアップロードしてください. 対象:", en: "Upload Files for " });
messageBuilderState.setMessage(__filename, "back", { ja: "戻る", en: "back" });
messageBuilderState.setMessage(__filename, "select", { ja: "ファイル選択", en: "select file" });
messageBuilderState.setMessage(__filename, "upload", { ja: "アップロード", en: "upload" });
messageBuilderState.setMessage(__filename, "uploading", { ja: "アップロード中", en: "uploading" });
messageBuilderState.setMessage(__filename, "alert-model-ext", {
"ja": "ファイルの拡張子は次のモノである必要があります。",
"en": "extension of file should be the following."
})
ja: "ファイルの拡張子は次のモノである必要があります。",
en: "extension of file should be the following.",
});
messageBuilderState.setMessage(__filename, "alert-model-file", {
"ja": "ファイルが選択されていません",
"en": "file is not selected."
})
}, [])
ja: "ファイルが選択されていません",
en: "file is not selected.",
});
}, []);
useEffect(() => {
setUploadSetting({
@ -41,162 +41,224 @@ export const FileUploaderScreen = (props: FileUploaderScreenProps) => {
isSampleMode: false,
sampleId: null,
files: [],
params: {}
})
}, [props.targetIndex, voiceChangerType])
params: {},
});
}, [props.targetIndex, voiceChangerType]);
const screen = useMemo(() => {
if (props.screen != "FileUploader") {
return <></>
return <></>;
}
const vcTypeOptions = (
Object.values(VoiceChangerType).map(x => {
return <option key={x} value={x}>{x}</option>
})
)
const vcTypeOptions = Object.values(VoiceChangerType).map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
});
const checkModelSetting = (setting: ModelUploadSetting) => {
if (setting.voiceChangerType == "RVC") {
const enough = !!setting.files.find(x => { return x.kind == "rvcModel" })
return enough
const enough = !!setting.files.find((x) => {
return x.kind == "rvcModel";
});
return enough;
} else if (setting.voiceChangerType == "MMVCv13") {
const enough = !!setting.files.find(x => { return x.kind == "mmvcv13Model" }) &&
!!setting.files.find(x => { return x.kind == "mmvcv13Config" })
return enough
const enough =
!!setting.files.find((x) => {
return x.kind == "mmvcv13Model";
}) &&
!!setting.files.find((x) => {
return x.kind == "mmvcv13Config";
});
return enough;
} else if (setting.voiceChangerType == "MMVCv15") {
const enough = !!setting.files.find(x => { return x.kind == "mmvcv15Model" }) &&
!!setting.files.find(x => { return x.kind == "mmvcv15Config" })
return enough
const enough =
!!setting.files.find((x) => {
return x.kind == "mmvcv15Model";
}) &&
!!setting.files.find((x) => {
return x.kind == "mmvcv15Config";
});
return enough;
} else if (setting.voiceChangerType == "so-vits-svc-40") {
const enough = !!setting.files.find(x => { return x.kind == "soVitsSvc40Config" }) &&
!!setting.files.find(x => { return x.kind == "soVitsSvc40Model" })
return enough
const enough =
!!setting.files.find((x) => {
return x.kind == "soVitsSvc40Config";
}) &&
!!setting.files.find((x) => {
return x.kind == "soVitsSvc40Model";
});
return enough;
} else if (setting.voiceChangerType == "DDSP-SVC") {
const enough = !!setting.files.find(x => { return x.kind == "ddspSvcModel" }) &&
!!setting.files.find(x => { return x.kind == "ddspSvcModelConfig" }) &&
!!setting.files.find(x => { return x.kind == "ddspSvcDiffusion" }) &&
!!setting.files.find(x => { return x.kind == "ddspSvcDiffusionConfig" })
return enough
const enough =
!!setting.files.find((x) => {
return x.kind == "ddspSvcModel";
}) &&
!!setting.files.find((x) => {
return x.kind == "ddspSvcModelConfig";
}) &&
!!setting.files.find((x) => {
return x.kind == "ddspSvcDiffusion";
}) &&
!!setting.files.find((x) => {
return x.kind == "ddspSvcDiffusionConfig";
});
return enough;
} else if (setting.voiceChangerType == "Diffusion-SVC") {
const enough = !!setting.files.find((x) => {
return x.kind == "diffusionSVCModel";
});
return enough;
} else if (setting.voiceChangerType == "Beatrice") {
const enough = !!setting.files.find((x) => {
return x.kind == "beatriceModel";
});
return enough;
} else if (setting.voiceChangerType == "LLVC") {
const enough =
!!setting.files.find((x) => {
return x.kind == "llvcModel";
}) &&
!!setting.files.find((x) => {
return x.kind == "llvcConfig";
});
return enough;
} else if (setting.voiceChangerType == "EasyVC") {
const enough = !!setting.files.find((x) => {
return x.kind == "easyVCModel";
});
return enough;
}
return false
}
return false;
};
const generateFileRow = (setting: ModelUploadSetting, title: string, kind: ModelFileKind, ext: string[], dir: string = "") => {
const selectedFile = setting.files.find(x => { return x.kind == kind })
const selectedFilename = selectedFile?.file.name || ""
const selectedFile = setting.files.find((x) => {
return x.kind == kind;
});
const selectedFilename = selectedFile?.file.name || "";
return (
<div key={`${title}`} className="file-uploader-file-select-row">
<div className="file-uploader-file-select-row-label">{title}:</div>
<div className="file-uploader-file-select-row-value" >
{trimfileName(selectedFilename, 30)}
</div>
<div className="file-uploader-file-select-row-button" onClick={async () => {
const file = await fileSelector("")
if (checkExtention(file.name, ext) == false) {
const alertMessage = `${messageBuilderState.getMessage(__filename, "alert-model-ext")} ${ext}`
alert(alertMessage)
return
}
if (selectedFile) {
selectedFile.file = file
} else {
setting.files.push({ kind: kind, file: file, dir: dir })
}
setUploadSetting({ ...setting })
}}>
<div className="file-uploader-file-select-row-value">{trimfileName(selectedFilename, 30)}</div>
<div
className="file-uploader-file-select-row-button"
onClick={async () => {
const file = await fileSelector("");
if (checkExtention(file.name, ext) == false) {
const alertMessage = `${messageBuilderState.getMessage(__filename, "alert-model-ext")} ${ext}`;
alert(alertMessage);
return;
}
if (selectedFile) {
selectedFile.file = file;
} else {
setting.files.push({ kind: kind, file: file, dir: dir });
}
setUploadSetting({ ...setting });
}}
>
{messageBuilderState.getMessage(__filename, "select")}
</div>
</div>
)
}
);
};
const generateFileRowsByVCType = (vcType: VoiceChangerType) => {
const rows: JSX.Element[] = []
const rows: JSX.Element[] = [];
if (vcType == "RVC") {
rows.push(generateFileRow(uploadSetting!, "Model", "rvcModel", ["pth", "onnx"]))
rows.push(generateFileRow(uploadSetting!, "Index", "rvcIndex", ["index", "bin"]))
rows.push(generateFileRow(uploadSetting!, "Model", "rvcModel", ["pth", "onnx"]));
rows.push(generateFileRow(uploadSetting!, "Index", "rvcIndex", ["index", "bin"]));
} else if (vcType == "MMVCv13") {
rows.push(generateFileRow(uploadSetting!, "Config", "mmvcv13Config", ["json"]))
rows.push(generateFileRow(uploadSetting!, "Model", "mmvcv13Model", ["pth", "onnx"]))
rows.push(generateFileRow(uploadSetting!, "Config", "mmvcv13Config", ["json"]));
rows.push(generateFileRow(uploadSetting!, "Model", "mmvcv13Model", ["pth", "onnx"]));
} else if (vcType == "MMVCv15") {
rows.push(generateFileRow(uploadSetting!, "Config", "mmvcv15Config", ["json"]))
rows.push(generateFileRow(uploadSetting!, "Corres", "mmvcv15Correspondence", ["txt"]))
rows.push(generateFileRow(uploadSetting!, "Model", "mmvcv15Model", ["pth", "onnx"]))
rows.push(generateFileRow(uploadSetting!, "Config", "mmvcv15Config", ["json"]));
rows.push(generateFileRow(uploadSetting!, "Corres", "mmvcv15Correspondence", ["txt"]));
rows.push(generateFileRow(uploadSetting!, "Model", "mmvcv15Model", ["pth", "onnx"]));
} else if (vcType == "so-vits-svc-40") {
rows.push(generateFileRow(uploadSetting!, "Config", "soVitsSvc40Config", ["json"]))
rows.push(generateFileRow(uploadSetting!, "Model", "soVitsSvc40Model", ["pth"]))
rows.push(generateFileRow(uploadSetting!, "Cluster", "soVitsSvc40Cluster", ["pth", "pt"]))
rows.push(generateFileRow(uploadSetting!, "Config", "soVitsSvc40Config", ["json"]));
rows.push(generateFileRow(uploadSetting!, "Model", "soVitsSvc40Model", ["pth"]));
rows.push(generateFileRow(uploadSetting!, "Cluster", "soVitsSvc40Cluster", ["pth", "pt"]));
} else if (vcType == "DDSP-SVC") {
rows.push(generateFileRow(uploadSetting!, "Config", "ddspSvcModelConfig", ["yaml"], "model/"))
rows.push(generateFileRow(uploadSetting!, "Model", "ddspSvcModel", ["pth", "pt"], "model/"))
rows.push(generateFileRow(uploadSetting!, "Config(diff)", "ddspSvcDiffusionConfig", ["yaml"], "diff/"))
rows.push(generateFileRow(uploadSetting!, "Model(diff)", "ddspSvcDiffusion", ["pth", "pt"], "diff/"))
rows.push(generateFileRow(uploadSetting!, "Config", "ddspSvcModelConfig", ["yaml"], "model/"));
rows.push(generateFileRow(uploadSetting!, "Model", "ddspSvcModel", ["pth", "pt"], "model/"));
rows.push(generateFileRow(uploadSetting!, "Config(diff)", "ddspSvcDiffusionConfig", ["yaml"], "diff/"));
rows.push(generateFileRow(uploadSetting!, "Model(diff)", "ddspSvcDiffusion", ["pth", "pt"], "diff/"));
} else if (vcType == "Diffusion-SVC") {
rows.push(generateFileRow(uploadSetting!, "Model(combo)", "diffusionSVCModel", ["ptc"]));
} else if (vcType == "Beatrice") {
rows.push(generateFileRow(uploadSetting!, "Beatrice", "beatriceModel", ["bin"]));
} else if (vcType == "LLVC") {
rows.push(generateFileRow(uploadSetting!, "Model", "llvcModel", ["pth"]));
rows.push(generateFileRow(uploadSetting!, "Config", "llvcConfig", ["json"]));
} else if (vcType == "EasyVC") {
rows.push(generateFileRow(uploadSetting!, "Model", "easyVCModel", ["onnx"]));
}
return rows
}
const fileRows = generateFileRowsByVCType(voiceChangerType)
return rows;
};
const fileRows = generateFileRowsByVCType(voiceChangerType);
// appState.serverSetting.uploadProgress == 0 ? `loading model...(wait about 20sec)` : `processing.... ${appState.serverSetting.uploadProgress.toFixed(1)}%` : ""
const buttonLabel = serverSetting.uploadProgress == 0 ?
messageBuilderState.getMessage(__filename, "upload") :
messageBuilderState.getMessage(__filename, "uploading") + `(${serverSetting.uploadProgress.toFixed(1)}%)`
const buttonLabel = serverSetting.uploadProgress == 0 ? messageBuilderState.getMessage(__filename, "upload") : messageBuilderState.getMessage(__filename, "uploading") + `(${serverSetting.uploadProgress.toFixed(1)}%)`;
return (
<div className="dialog-frame">
<div className="dialog-title">File Uploader</div>
<div className="dialog-fixed-size-content">
<div className="file-uploader-header">
{messageBuilderState.getMessage(__filename, "header_message")} Slot[{props.targetIndex}]
<span onClick={() => {
props.backToSlotManager()
}} className="file-uploader-header-button">&lt;&lt;{messageBuilderState.getMessage(__filename, "back")}</span></div>
<div className="file-uploader-voice-changer-select" >VoiceChangerType:
<select value={voiceChangerType} onChange={(e) => {
setVoiceChangerType(e.target.value as VoiceChangerType)
}}>
<span
onClick={() => {
props.backToSlotManager();
}}
className="file-uploader-header-button"
>
&lt;&lt;{messageBuilderState.getMessage(__filename, "back")}
</span>
</div>
<div className="file-uploader-voice-changer-select">
VoiceChangerType:
<select
value={voiceChangerType}
onChange={(e) => {
setVoiceChangerType(e.target.value as VoiceChangerType);
}}
>
{vcTypeOptions}
</select>
</div>
<div className="file-uploader-file-select-container">
{fileRows}
</div>
<div className="file-uploader-file-select-container">{fileRows}</div>
<div className="file-uploader-file-select-upload-button-container">
<div className="file-uploader-file-select-upload-button" onClick={() => {
if (!uploadSetting) {
return
}
if (serverSetting.uploadProgress != 0) {
return
}
if (checkModelSetting(uploadSetting)) {
serverSetting.uploadModel(uploadSetting).then(() => {
props.backToSlotManager()
})
} else {
const errorMessage = messageBuilderState.getMessage(__filename, "alert-model-file")
alert(errorMessage)
}
}}>
<div
className="file-uploader-file-select-upload-button"
onClick={() => {
if (!uploadSetting) {
return;
}
if (serverSetting.uploadProgress != 0) {
return;
}
if (checkModelSetting(uploadSetting)) {
serverSetting.uploadModel(uploadSetting).then(() => {
props.backToSlotManager();
});
} else {
const errorMessage = messageBuilderState.getMessage(__filename, "alert-model-file");
alert(errorMessage);
}
}}
>
{buttonLabel}
</div>
</div>
</div>
</div>
)
}, [
props.screen,
props.targetIndex,
voiceChangerType,
uploadSetting,
serverSetting.uploadModel,
serverSetting.uploadProgress
])
);
}, [props.screen, props.targetIndex, voiceChangerType, uploadSetting, serverSetting.uploadModel, serverSetting.uploadProgress]);
return screen;
};

View File

@ -6,68 +6,54 @@ import { FileUploaderScreen } from "./904-3_FileUploader";
import { EditorScreen } from "./904-4_Editor";
export type uploadData = {
slot: number
model: File | null
index: File | null
}
slot: number;
model: File | null;
index: File | null;
};
export const ModelSlotSettingMode = {
"localFile": "localFile",
"fromNet": "fromNet"
} as const
export type ModelSlotSettingMode = typeof ModelSlotSettingMode[keyof typeof ModelSlotSettingMode]
localFile: "localFile",
fromNet: "fromNet",
} as const;
export type ModelSlotSettingMode = (typeof ModelSlotSettingMode)[keyof typeof ModelSlotSettingMode];
export const ModelSlotManagerDialogScreen = {
"Main": "Main",
"SampleDownloader": "SampleDownloader",
"FileUploader": "FileUploader",
"Editor": "Editor"
} as const
export type ModelSlotManagerDialogScreen = typeof ModelSlotManagerDialogScreen[keyof typeof ModelSlotManagerDialogScreen]
Main: "Main",
SampleDownloader: "SampleDownloader",
FileUploader: "FileUploader",
Editor: "Editor",
} as const;
export type ModelSlotManagerDialogScreen = (typeof ModelSlotManagerDialogScreen)[keyof typeof ModelSlotManagerDialogScreen];
export const ModelSlotManagerDialog = () => {
const guiState = useGuiState()
const [screen, setScreen] = useState<ModelSlotManagerDialogScreen>("Main")
const [targetIndex, setTargetIndex] = useState<number>(0)
const guiState = useGuiState();
const [screen, setScreen] = useState<ModelSlotManagerDialogScreen>("Main");
const [targetIndex, setTargetIndex] = useState<number>(0);
const dialog = useMemo(() => {
const close = () => { guiState.stateControls.showModelSlotManagerCheckbox.updateState(false) }
const openSampleDownloader = (index: number) => { setTargetIndex(index); setScreen("SampleDownloader") }
const openFileUploader = (index: number) => { setTargetIndex(index); setScreen("FileUploader") }
const openEditor = (index: number) => { setTargetIndex(index); setScreen("Editor") }
const close = () => {
guiState.stateControls.showModelSlotManagerCheckbox.updateState(false);
};
const openSampleDownloader = (index: number) => {
setTargetIndex(index);
setScreen("SampleDownloader");
};
const openFileUploader = (index: number) => {
setTargetIndex(index);
setScreen("FileUploader");
};
const openEditor = (index: number) => {
setTargetIndex(index);
setScreen("Editor");
};
const backToSlotManager = () => { setScreen("Main") }
const mainScreen = (
<MainScreen
screen={screen}
close={close}
openSampleDownloader={openSampleDownloader}
openFileUploader={openFileUploader}
openEditor={openEditor}
/>
)
const sampleDownloaderScreen = (
<SampleDownloaderScreen
screen={screen}
targetIndex={targetIndex}
close={close}
backToSlotManager={backToSlotManager} />
)
const fileUploaderScreen = (
<FileUploaderScreen
screen={screen}
targetIndex={targetIndex}
close={close}
backToSlotManager={backToSlotManager} />
)
const editorScreen = (
<EditorScreen
screen={screen}
targetIndex={targetIndex}
close={close}
backToSlotManager={backToSlotManager} />
)
const backToSlotManager = () => {
setScreen("Main");
};
const mainScreen = <MainScreen screen={screen} close={close} openSampleDownloader={openSampleDownloader} openFileUploader={openFileUploader} openEditor={openEditor} />;
const sampleDownloaderScreen = <SampleDownloaderScreen screen={screen} targetIndex={targetIndex} close={close} backToSlotManager={backToSlotManager} />;
const fileUploaderScreen = <FileUploaderScreen screen={screen} targetIndex={targetIndex} close={close} backToSlotManager={backToSlotManager} />;
const editorScreen = <EditorScreen screen={screen} targetIndex={targetIndex} close={close} backToSlotManager={backToSlotManager} />;
return (
<div className="dialog-frame">
{mainScreen}
@ -75,10 +61,8 @@ export const ModelSlotManagerDialog = () => {
{fileUploaderScreen}
{editorScreen}
</div>
)
}, [screen, targetIndex])
);
}, [screen, targetIndex]);
return dialog;
};

View File

@ -3,158 +3,182 @@ import { useGuiState } from "./001_GuiStateProvider";
import { useAppState } from "../../001_provider/001_AppStateProvider";
import { MergeElement, RVCModelSlot, RVCModelType, VoiceChangerType } from "@dannadori/voice-changer-client-js";
export const MergeLabDialog = () => {
const guiState = useGuiState()
const guiState = useGuiState();
const { serverSetting } = useAppState()
const [currentFilter, setCurrentFilter] = useState<string>("")
const [mergeElements, setMergeElements] = useState<MergeElement[]>([])
const { serverSetting } = useAppState();
const [currentFilter, setCurrentFilter] = useState<string>("");
const [mergeElements, setMergeElements] = useState<MergeElement[]>([]);
// スロットが変更されたときの初期化処理
const newSlotChangeKey = useMemo(() => {
if (!serverSetting.serverSetting.modelSlots) {
return ""
return "";
}
return serverSetting.serverSetting.modelSlots.reduce((prev, cur) => {
return prev + "_" + cur.modelFile
}, "")
}, [serverSetting.serverSetting.modelSlots])
return prev + "_" + cur.modelFile;
}, "");
}, [serverSetting.serverSetting.modelSlots]);
const filterItems = useMemo(() => {
return serverSetting.serverSetting.modelSlots.reduce((prev, cur) => {
if (cur.voiceChangerType != "RVC") {
return prev
}
const curRVC = cur as RVCModelSlot
const key = `${curRVC.modelType},${cur.samplingRate},${curRVC.embChannels}`
const val = { type: curRVC.modelType, samplingRate: cur.samplingRate, embChannels: curRVC.embChannels }
const existKeys = Object.keys(prev)
if (!cur.modelFile || cur.modelFile.length == 0) {
return prev
}
if (curRVC.modelType == "onnxRVC" || curRVC.modelType == "onnxRVCNono") {
return prev
}
if (!existKeys.includes(key)) {
prev[key] = val
}
return prev
}, {} as { [key: string]: { type: RVCModelType, samplingRate: number, embChannels: number } })
}, [newSlotChangeKey])
return serverSetting.serverSetting.modelSlots.reduce(
(prev, cur) => {
if (cur.voiceChangerType != "RVC") {
return prev;
}
const curRVC = cur as RVCModelSlot;
const key = `${curRVC.modelType},${cur.samplingRate},${curRVC.embChannels}`;
const val = { type: curRVC.modelType, samplingRate: cur.samplingRate, embChannels: curRVC.embChannels };
const existKeys = Object.keys(prev);
if (!cur.modelFile || cur.modelFile.length == 0) {
return prev;
}
if (curRVC.modelType == "onnxRVC" || curRVC.modelType == "onnxRVCNono") {
return prev;
}
if (!existKeys.includes(key)) {
prev[key] = val;
}
return prev;
},
{} as { [key: string]: { type: RVCModelType; samplingRate: number; embChannels: number } },
);
}, [newSlotChangeKey]);
const models = useMemo(() => {
return serverSetting.serverSetting.modelSlots.filter(x => {
return serverSetting.serverSetting.modelSlots.filter((x) => {
if (x.voiceChangerType != "RVC") {
return
return;
}
const xRVC = x as RVCModelSlot
const filterVals = filterItems[currentFilter]
const xRVC = x as RVCModelSlot;
const filterVals = filterItems[currentFilter];
if (!filterVals) {
return false
return false;
}
if (xRVC.modelType == filterVals.type && xRVC.samplingRate == filterVals.samplingRate && xRVC.embChannels == filterVals.embChannels) {
return true
return true;
} else {
return false
return false;
}
})
}, [filterItems, currentFilter])
});
}, [filterItems, currentFilter]);
useEffect(() => {
if (Object.keys(filterItems).length > 0) {
setCurrentFilter(Object.keys(filterItems)[0])
setCurrentFilter(Object.keys(filterItems)[0]);
}
}, [filterItems])
}, [filterItems]);
useEffect(() => {
// models はフィルタ後の配列
const newMergeElements = models.map((x) => {
return { filename: x.modelFile, strength: 0 }
})
setMergeElements(newMergeElements)
}, [models])
return { slotIndex: x.slotIndex, filename: x.modelFile, strength: 0 };
});
setMergeElements(newMergeElements);
}, [models]);
const dialog = useMemo(() => {
const closeButtonRow = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-item-text">
</div>
<div className="body-item-text"></div>
<div className="body-button-container body-button-container-space-around">
<div className="body-button" onClick={() => { guiState.stateControls.showMergeLabCheckbox.updateState(false) }} >close</div>
<div
className="body-button"
onClick={() => {
guiState.stateControls.showMergeLabCheckbox.updateState(false);
}}
>
close
</div>
</div>
<div className="body-item-text"></div>
</div>
)
);
const filterOptions = Object.keys(filterItems).map(x => {
return <option key={x} value={x}>{x}</option>
}).filter(x => x != null)
const onMergeElementsChanged = (filename: string, strength: number) => {
const newMergeElements = mergeElements.map((x) => {
if (x.filename == filename) {
return { filename: x.filename, strength: strength }
} else {
return x
}
const filterOptions = Object.keys(filterItems)
.map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
})
setMergeElements(newMergeElements)
}
.filter((x) => x != null);
const onMergeElementsChanged = (slotIndex: number, strength: number) => {
const newMergeElements = mergeElements.map((x) => {
if (x.slotIndex == slotIndex) {
return { slotIndex: x.slotIndex, strength: strength };
} else {
return x;
}
});
setMergeElements(newMergeElements);
};
const onMergeClicked = () => {
const validMergeElements = mergeElements.filter((x) => {
return x.strength > 0;
});
serverSetting.mergeModel({
voiceChangerType: VoiceChangerType.RVC,
command: "mix",
files: mergeElements
})
}
files: validMergeElements,
});
};
const modelList = mergeElements.map((x, index) => {
const name = models.find(model => { return model.modelFile == x.filename })?.name || ""
const name =
models.find((model) => {
return model.slotIndex == x.slotIndex;
})?.name || "";
return (
<div key={index} className="merge-lab-model-item">
<div>{name}</div>
<div>
{name}
</div>
<div>
<input type="range" className="body-item-input-slider" min="0" max="100" step="1" value={x.strength} onChange={(e) => {
onMergeElementsChanged(x.filename, Number(e.target.value))
}}></input>
<input
type="range"
className="body-item-input-slider"
min="0"
max="100"
step="1"
value={x.strength}
onChange={(e) => {
onMergeElementsChanged(x.slotIndex, Number(e.target.value));
}}
></input>
<span className="body-item-input-slider-val">{x.strength}</span>
</div>
</div>
)
})
);
});
const content = (
<div className="merge-lab-container">
<div className="merge-lab-type-filter">
<div>Type:</div>
<div>
Type:
</div>
<div>
<select value={currentFilter} onChange={(e) => { setCurrentFilter(e.target.value) }}>
<select
value={currentFilter}
onChange={(e) => {
setCurrentFilter(e.target.value);
}}
>
{filterOptions}
</select>
</div>
</div>
<div className="merge-lab-manipulator">
<div className="merge-lab-model-list">
{modelList}
</div>
<div className="merge-lab-model-list">{modelList}</div>
<div className="merge-lab-merge-buttons">
<div className="merge-lab-merge-buttons-notice">
The merged model is stored in the final slot. If you assign this slot, it will be overwritten.
</div>
<div className="merge-lab-merge-buttons-notice">The merged model is stored in the final slot. If you assign this slot, it will be overwritten.</div>
<div className="merge-lab-merge-button" onClick={onMergeClicked}>
merge
</div>
</div>
</div>
</div>
)
);
return (
<div className="dialog-frame">
<div className="dialog-title">MergeLab</div>
@ -166,5 +190,4 @@ export const MergeLabDialog = () => {
);
}, [newSlotChangeKey, currentFilter, mergeElements, models]);
return dialog;
};

View File

@ -3,159 +3,215 @@ import { useGuiState } from "./001_GuiStateProvider";
import { useAppState } from "../../001_provider/001_AppStateProvider";
import { CrossFadeOverlapSize, Protocol } from "@dannadori/voice-changer-client-js";
export const AdvancedSettingDialog = () => {
const guiState = useGuiState()
const { setting, serverSetting, setWorkletNodeSetting, setWorkletSetting } = useAppState()
const guiState = useGuiState();
const { setting, serverSetting, setWorkletNodeSetting, setWorkletSetting, setVoiceChangerClientSetting } = useAppState();
const dialog = useMemo(() => {
const closeButtonRow = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-item-text">
</div>
<div className="body-item-text"></div>
<div className="body-button-container body-button-container-space-around">
<div className="body-button" onClick={() => { guiState.stateControls.showAdvancedSettingCheckbox.updateState(false) }} >close</div>
<div
className="body-button"
onClick={() => {
guiState.stateControls.showAdvancedSettingCheckbox.updateState(false);
}}
>
close
</div>
</div>
<div className="body-item-text"></div>
</div>
)
);
const onProtocolChanged = async (val: Protocol) => {
setWorkletNodeSetting({ ...setting.workletNodeSetting, protocol: val })
}
setWorkletNodeSetting({ ...setting.workletNodeSetting, protocol: val });
};
const protocolRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
protocol
</div>
<div className="advanced-setting-container-row-title">protocol</div>
<div className="advanced-setting-container-row-field">
<select value={setting.workletNodeSetting.protocol} onChange={(e) => {
onProtocolChanged(e.target.value as
Protocol)
}}>
{
Object.values(Protocol).map(x => {
return <option key={x} value={x}>{x}</option>
})
}
<select
value={setting.workletNodeSetting.protocol}
onChange={(e) => {
onProtocolChanged(e.target.value as Protocol);
}}
>
{Object.values(Protocol).map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
})}
</select>
</div>
</div>
)
);
const crossfaceRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
Crossfade
</div>
<div className="advanced-setting-container-row-title">Crossfade</div>
<div className="advanced-setting-container-row-field">
<div className="advanced-setting-container-row-field-crossfade-container">
<div>
<div>overlap:</div>
<div>
<select className="body-select" value={serverSetting.serverSetting.crossFadeOverlapSize} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeOverlapSize: Number(e.target.value) as CrossFadeOverlapSize })
}}>
{
Object.values(CrossFadeOverlapSize).map(x => {
return <option key={x} value={x}>{x}</option>
})
}
<select
className="body-select"
value={serverSetting.serverSetting.crossFadeOverlapSize}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeOverlapSize: Number(e.target.value) as CrossFadeOverlapSize });
}}
>
{Object.values(CrossFadeOverlapSize).map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
})}
</select>
</div>
</div>
<div>
<div>start:</div>
<div>
<input type="number" min={0} max={1} step={0.1} value={serverSetting.serverSetting.crossFadeOffsetRate} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeOffsetRate: Number(e.target.value) })
}} />
<input
type="number"
min={0}
max={1}
step={0.1}
value={serverSetting.serverSetting.crossFadeOffsetRate}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeOffsetRate: Number(e.target.value) });
}}
/>
</div>
</div>
<div>
<div>end:</div>
<div>
<input type="number" min={0} max={1} step={0.1} value={serverSetting.serverSetting.crossFadeEndRate} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeEndRate: Number(e.target.value) })
}} />
<input
type="number"
min={0}
max={1}
step={0.1}
value={serverSetting.serverSetting.crossFadeEndRate}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, crossFadeEndRate: Number(e.target.value) });
}}
/>
</div>
</div>
</div>
</div>
</div>
)
);
const trancateRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
Trancate
</div>
<div className="advanced-setting-container-row-title">Trancate</div>
<div className="advanced-setting-container-row-field">
<input type="number" min={5} max={300} step={1} value={setting.workletSetting.numTrancateTreshold} onChange={(e) => {
setWorkletSetting({
...setting.workletSetting,
numTrancateTreshold: Number(e.target.value)
})
}} />
<input
type="number"
min={5}
max={300}
step={1}
value={setting.workletSetting.numTrancateTreshold}
onChange={(e) => {
setWorkletSetting({
...setting.workletSetting,
numTrancateTreshold: Number(e.target.value),
});
}}
/>
</div>
</div>
)
);
const onSilenceFrontChanged = (val: number) => {
serverSetting.updateServerSettings({
...serverSetting.serverSetting,
silenceFront: val
})
}
silenceFront: val,
});
};
const silenceFrontRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
SilenceFront
</div>
<div className="advanced-setting-container-row-title">SilenceFront</div>
<div className="advanced-setting-container-row-field">
<select value={serverSetting.serverSetting.silenceFront} onChange={(e) => { onSilenceFrontChanged(Number(e.target.value)) }}>
<option value="0" >off</option>
<option value="1" >on</option>
<select
value={serverSetting.serverSetting.silenceFront}
onChange={(e) => {
onSilenceFrontChanged(Number(e.target.value));
}}
>
<option value="0">off</option>
<option value="1">on</option>
</select>
</div>
</div>
)
);
const protectRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
Protect
</div>
<div className="advanced-setting-container-row-title">Protect</div>
<div className="advanced-setting-container-row-field">
<div>
<input type="range" className="body-item-input-slider" min="0" max="0.5" step="0.1" value={serverSetting.serverSetting.protect || 0} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, protect: Number(e.target.value) })
}}></input>
<input
type="range"
className="body-item-input-slider"
min="0"
max="0.5"
step="0.1"
value={serverSetting.serverSetting.protect || 0}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, protect: Number(e.target.value) });
}}
></input>
<span className="body-item-input-slider-val">{serverSetting.serverSetting.protect}</span>
</div>
</div>
</div>
)
);
const onRVCQualityChanged = (val: number) => {
serverSetting.updateServerSettings({
...serverSetting.serverSetting,
rvcQuality: val
})
}
rvcQuality: val,
});
};
const rvcQualityRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title">
RVC Quality
</div>
<div className="advanced-setting-container-row-title">RVC Quality</div>
<div className="advanced-setting-container-row-field">
<select value={serverSetting.serverSetting.rvcQuality} onChange={(e) => { onRVCQualityChanged(Number(e.target.value)) }}>
<option value="0" >low</option>
<option value="1" >high</option>
<select
value={serverSetting.serverSetting.rvcQuality}
onChange={(e) => {
onRVCQualityChanged(Number(e.target.value));
}}
>
<option value="0">low</option>
<option value="1">high</option>
</select>
</div>
</div>
)
);
const skipPassThroughConfirmationRow = (
<div className="advanced-setting-container-row">
<div className="advanced-setting-container-row-title-long">Skip Pass through confirmation</div>
<div className="advanced-setting-container-row-field">
<select
value={setting.voiceChangerClientSetting.passThroughConfirmationSkip ? "1" : "0"}
onChange={(e) => {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, passThroughConfirmationSkip: e.target.value == "1" ? true : false });
}}
>
<option value="0">No</option>
<option value="1">Yes</option>
</select>
</div>
</div>
);
const content = (
<div className="advanced-setting-container">
{protocolRow}
@ -164,8 +220,9 @@ export const AdvancedSettingDialog = () => {
{silenceFrontRow}
{protectRow}
{rvcQualityRow}
{skipPassThroughConfirmationRow}
</div>
)
);
return (
<div className="dialog-frame">
@ -178,5 +235,4 @@ export const AdvancedSettingDialog = () => {
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, setting.workletNodeSetting, setWorkletNodeSetting, setting.workletSetting, setWorkletSetting]);
return dialog;
};

View File

@ -0,0 +1,47 @@
import React, { useMemo } from "react";
import { useGuiState } from "./001_GuiStateProvider";
import { useAppState } from "../../001_provider/001_AppStateProvider";
import { useAppRoot } from "../../001_provider/001_AppRootProvider";
export const EnablePassThroughDialog = () => {
const guiState = useGuiState();
const { audioContextState } = useAppRoot();
const { serverSetting } = useAppState();
const { setting } = useAppState();
const dialog = useMemo(() => {
const buttonRow = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-item-text"></div>
<div className="body-button-container body-button-container-space-around">
<div
className="body-button"
onClick={() => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, passThrough: true });
guiState.stateControls.showEnablePassThroughDialogCheckbox.updateState(false);
}}
>
OK
</div>
<div
className="body-button"
onClick={() => {
guiState.stateControls.showEnablePassThroughDialogCheckbox.updateState(false);
}}
>
Cancel
</div>
</div>
<div className="body-item-text"></div>
</div>
);
console.log("AUDIO_CONTEXT", audioContextState.audioContext);
return (
<div className="dialog-frame">
<div className="dialog-title">Enable Pass Through</div>
<div className="dialog-content">{buttonRow}</div>
</div>
);
}, [setting, audioContextState, serverSetting.serverSetting]);
return dialog;
};

View File

@ -1,19 +1,22 @@
import React from "react";
import { useGuiState } from "./001_GuiStateProvider";
import { TextInputDialog } from "./911_TextInputDialog";
import { ShowLicenseDialog } from "./912_ShowLicenseDialog";
export const Dialogs2 = () => {
const guiState = useGuiState()
const guiState = useGuiState();
const dialogs = (
<div>
{guiState.stateControls.showTextInputCheckbox.trigger}
{guiState.stateControls.showLicenseCheckbox.trigger}
<div className="dialog-container2" id="dialog2">
{guiState.stateControls.showTextInputCheckbox.trigger}
<TextInputDialog></TextInputDialog>
{guiState.stateControls.showLicenseCheckbox.trigger}
<ShowLicenseDialog />
</div>
</div>
);
return dialogs
}
return dialogs;
};

View File

@ -0,0 +1,76 @@
////// Currently not used /////////
import React, { useMemo } from "react";
import { useGuiState } from "./001_GuiStateProvider";
import { isDesktopApp } from "../../const";
import { useMessageBuilder } from "../../hooks/useMessageBuilder";
export const ShowLicenseDialog = () => {
const guiState = useGuiState();
const messageBuilderState = useMessageBuilder();
useMemo(() => {
messageBuilderState.setMessage(__filename, "nsf_hifigan1", {
ja: "Diffusion SVC, DDSP SVCはvocodeerはDiffSinger Community Vocodersを使用しています。次のリンクからライセンスをご確認ください。",
en: "Diffusion SVC and DDSP SVC uses DiffSinger Community Vocoders. Please check the license from the following link.",
});
messageBuilderState.setMessage(__filename, "nsf_hifigan2", { ja: "別のモデルを使用する場合はpretrain\\nsf_hifiganに設置してください。", en: "Please place it on pretrain\\nsf_hifigan if you are using a different model." });
}, []);
const hifiGanLink = useMemo(() => {
return isDesktopApp() ? (
// @ts-ignore
<span
className="link"
onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://openvpi.github.io/vocoders/");
}}
>
license
</span>
) : (
<a className="link" href="https://openvpi.github.io/vocoders/" target="_blank" rel="noopener noreferrer">
license
</a>
);
}, []);
const dialog = useMemo(() => {
const hifiganMessage = (
<div className="dialog-content-part">
<div>{messageBuilderState.getMessage(__filename, "nsf_hifigan1")}</div>
<div>{messageBuilderState.getMessage(__filename, "nsf_hifigan2")}</div>
<div>{hifiGanLink}</div>
</div>
);
const buttonsRow = (
<div className="body-row split-3-4-3 left-padding-1">
<div className="body-item-text"></div>
<div className="body-button-container body-button-container-space-around">
<div
className="body-button"
onClick={() => {
guiState.stateControls.showLicenseCheckbox.updateState(false);
}}
>
close
</div>
</div>
<div className="body-item-text"></div>
</div>
);
return (
<div className="dialog-frame">
<div className="dialog-title">Input Dialog</div>
<div className="dialog-content">
<div className="body-row">{hifiganMessage}</div>
{buttonsRow}
</div>
</div>
);
}, [guiState.textInputResolve]);
return dialog;
};

View File

@ -5,122 +5,123 @@ import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useIndexedDB } from "@dannadori/voice-changer-client-js";
import { useMessageBuilder } from "../../../hooks/useMessageBuilder";
export type HeaderAreaProps = {
mainTitle: string
subTitle: string
}
mainTitle: string;
subTitle: string;
};
export const HeaderArea = (props: HeaderAreaProps) => {
const { appGuiSettingState } = useAppRoot()
const messageBuilderState = useMessageBuilder()
const { clearSetting } = useAppState()
const { appGuiSettingState } = useAppRoot();
const messageBuilderState = useMessageBuilder();
const { clearSetting, webInfoState } = useAppState();
const { removeItem } = useIndexedDB({ clientType: null })
const { removeItem, removeDB } = useIndexedDB({ clientType: null });
useMemo(() => {
messageBuilderState.setMessage(__filename, "github", { "ja": "github", "en": "github" })
messageBuilderState.setMessage(__filename, "manual", { "ja": "マニュアル", "en": "manual" })
messageBuilderState.setMessage(__filename, "screenCapture", { "ja": "録画ツール", "en": "Record Screen" })
messageBuilderState.setMessage(__filename, "support", { "ja": "支援", "en": "Donation" })
}, [])
messageBuilderState.setMessage(__filename, "github", { ja: "github", en: "github" });
messageBuilderState.setMessage(__filename, "manual", { ja: "マニュアル", en: "manual" });
messageBuilderState.setMessage(__filename, "screenCapture", { ja: "録画ツール", en: "Record Screen" });
messageBuilderState.setMessage(__filename, "support", { ja: "支援", en: "Donation" });
}, []);
const githubLink = useMemo(() => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer") }}>
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</span>
)
:
(
<a className="link tooltip" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</a>
)
}, [])
return isDesktopApp() ? (
<span
className="link tooltip"
onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer");
}}
>
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</span>
) : (
<a className="link tooltip" href="https://github.com/w-okada/voice-changer" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/github.svg" />
<div className="tooltip-text">{messageBuilderState.getMessage(__filename, "github")}</div>
</a>
);
}, []);
const manualLink = useMemo(() => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md") }}>
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "manual")}</div>
</span>
)
:
(
<a className="link tooltip" href="https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "manual")}</div>
</a>
)
}, [])
return isDesktopApp() ? (
<span
className="link tooltip"
onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md");
}}
>
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "manual")}</div>
</span>
) : (
<a className="link tooltip" href="https://github.com/w-okada/voice-changer/blob/master/tutorials/tutorial_rvc_ja_latest.md" target="_blank" rel="noopener noreferrer">
<img src="./assets/icons/help-circle.svg" />
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "manual")}</div>
</a>
);
}, []);
const toolLink = useMemo(() => {
return isDesktopApp() ?
(
<div className="link tooltip">
<img src="./assets/icons/tool.svg" />
<div className="tooltip-text tooltip-text-100px">
<p onClick={() => {
return isDesktopApp() ? (
<div className="link tooltip">
<img src="./assets/icons/tool.svg" />
<div className="tooltip-text tooltip-text-100px">
<p
onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://w-okada.github.io/screen-recorder-ts/")
}}>
{messageBuilderState.getMessage(__filename, "screenCapture")}
</p>
</div>
window.electronAPI.openBrowser("https://w-okada.github.io/screen-recorder-ts/");
}}
>
{messageBuilderState.getMessage(__filename, "screenCapture")}
</p>
</div>
)
:
(
<div className="link tooltip">
<img src="./assets/icons/tool.svg" />
<div className="tooltip-text tooltip-text-100px">
<p onClick={() => {
window.open("https://w-okada.github.io/screen-recorder-ts/", '_blank', "noreferrer")
}}>
{messageBuilderState.getMessage(__filename, "screenCapture")}
</p>
</div>
</div>
) : (
<div className="link tooltip">
<img src="./assets/icons/tool.svg" />
<div className="tooltip-text tooltip-text-100px">
<p
onClick={() => {
window.open("https://w-okada.github.io/screen-recorder-ts/", "_blank", "noreferrer");
}}
>
{messageBuilderState.getMessage(__filename, "screenCapture")}
</p>
</div>
)
}, [])
</div>
);
}, []);
const coffeeLink = useMemo(() => {
return isDesktopApp() ?
(
// @ts-ignore
<span className="link tooltip" onClick={() => { window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad") }}>
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "support")}</div>
</span>
)
:
(
<a className="link tooltip" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">
{messageBuilderState.getMessage(__filename, "support")}
</div>
</a>
)
}, [])
return isDesktopApp() ? (
<span
className="link tooltip"
onClick={() => {
// @ts-ignore
window.electronAPI.openBrowser("https://www.buymeacoffee.com/wokad");
}}
>
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "support")}</div>
</span>
) : (
<a className="link tooltip" href="https://www.buymeacoffee.com/wokad" target="_blank" rel="noopener noreferrer">
<img className="donate-img" src="./assets/buymeacoffee.png" />
<div className="tooltip-text tooltip-text-100px">{messageBuilderState.getMessage(__filename, "support")}</div>
</a>
);
}, []);
const headerArea = useMemo(() => {
const onClearSettingClicked = async () => {
await clearSetting()
await removeItem(INDEXEDDB_KEY_AUDIO_OUTPUT)
location.reload()
}
await clearSetting();
await removeItem(INDEXEDDB_KEY_AUDIO_OUTPUT);
await removeDB();
location.reload();
};
return (
<div className="headerArea">
@ -139,15 +140,16 @@ export const HeaderArea = (props: HeaderAreaProps) => {
{/* {licenseButton} */}
</span>
<span className="belongings">
<div className="belongings-button" onClick={onClearSettingClicked}>clear setting</div>
<div className="belongings-button" onClick={onClearSettingClicked}>
clear setting
</div>
{/* <div className="belongings-button" onClick={onReloadClicked}>reload</div>
<div className="belongings-button" onClick={onReselectVCClicked}>select vc</div> */}
</span>
</div>
</div>
)
}, [props.subTitle, props.mainTitle, appGuiSettingState.version, appGuiSettingState.edition])
);
}, [props.subTitle, props.mainTitle, appGuiSettingState.version, appGuiSettingState.edition]);
return headerArea
return headerArea;
};

View File

@ -1,83 +1,124 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
import { useGuiState } from "../001_GuiStateProvider"
import { useMessageBuilder } from "../../../hooks/useMessageBuilder"
import React, { useMemo, useState } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useGuiState } from "../001_GuiStateProvider";
import { useMessageBuilder } from "../../../hooks/useMessageBuilder";
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
export type ModelSlotAreaProps = {
}
export type ModelSlotAreaProps = {};
const SortTypes = {
slot: "slot",
name: "name",
} as const;
export type SortTypes = (typeof SortTypes)[keyof typeof SortTypes];
export const ModelSlotArea = (_props: ModelSlotAreaProps) => {
const { serverSetting, getInfo } = useAppState()
const guiState = useGuiState()
const messageBuilderState = useMessageBuilder()
const { serverSetting, getInfo, webEdition } = useAppState();
const guiState = useGuiState();
const messageBuilderState = useMessageBuilder();
const [sortType, setSortType] = useState<SortTypes>("slot");
useMemo(() => {
messageBuilderState.setMessage(__filename, "edit", { "ja": "編集", "en": "edit" })
}, [])
messageBuilderState.setMessage(__filename, "edit", { ja: "編集", en: "edit" });
}, []);
const modelTiles = useMemo(() => {
if (!serverSetting.serverSetting.modelSlots) {
return []
return [];
}
return serverSetting.serverSetting.modelSlots.map((x, index) => {
if (!x.modelFile || x.modelFile.length == 0) {
return null
}
const tileContainerClass = index == serverSetting.serverSetting.modelSlotIndex ? "model-slot-tile-container-selected" : "model-slot-tile-container"
const name = x.name.length > 8 ? x.name.substring(0, 7) + "..." : x.name
const iconElem = x.iconFile.length > 0 ?
<>
<img className="model-slot-tile-icon" src={x.iconFile} alt={x.name} />
<div className="model-slot-tile-vctype">{x.voiceChangerType}</div>
</>
:
<>
<div className="model-slot-tile-icon-no-entry">no image</div>
<div className="model-slot-tile-vctype">{x.voiceChangerType}</div>
</>
const modelSlots =
sortType == "slot"
? serverSetting.serverSetting.modelSlots
: serverSetting.serverSetting.modelSlots.slice().sort((a, b) => {
return a.name.localeCompare(b.name);
});
const clickAction = async () => {
const dummyModelSlotIndex = (Math.floor(Date.now() / 1000)) * 1000 + index
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, modelSlotIndex: dummyModelSlotIndex })
setTimeout(() => { // quick hack
getInfo()
}, 1000 * 2)
}
return modelSlots
.map((x, index) => {
if (!x.modelFile || x.modelFile.length == 0) {
return null;
}
const tileContainerClass = x.slotIndex == serverSetting.serverSetting.modelSlotIndex ? "model-slot-tile-container-selected" : "model-slot-tile-container";
const name = x.name.length > 8 ? x.name.substring(0, 7) + "..." : x.name;
return (
<div key={index} className={tileContainerClass} onClick={clickAction}>
<div className="model-slot-tile-icon-div">
{iconElem}
const modelDir = x.slotIndex == "Beatrice-JVS" ? "model_dir_static" : serverSetting.serverSetting.voiceChangerParams.model_dir;
const icon = x.iconFile.length > 0 ? modelDir + "/" + x.slotIndex + "/" + x.iconFile.split(/[\/\\]/).pop() : "./assets/icons/human.png";
const iconElem =
x.iconFile.length > 0 ? (
<>
{/* <img className="model-slot-tile-icon" src={serverSetting.serverSetting.voiceChangerParams.model_dir + "/" + x.slotIndex + "/" + x.iconFile.split(/[\/\\]/).pop()} alt={x.name} /> */}
<img className="model-slot-tile-icon" src={icon} alt={x.name} />
<div className="model-slot-tile-vctype">{x.voiceChangerType}</div>
</>
) : (
<>
<div className="model-slot-tile-icon-no-entry">no image</div>
<div className="model-slot-tile-vctype">{x.voiceChangerType}</div>
</>
);
const clickAction = async () => {
// @ts-ignore
const dummyModelSlotIndex = Math.floor(Date.now() / 1000) * 1000 + x.slotIndex;
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, modelSlotIndex: dummyModelSlotIndex });
setTimeout(() => {
// quick hack
getInfo();
}, 1000 * 2);
};
return (
<div key={index} className={tileContainerClass} onClick={clickAction}>
<div className="model-slot-tile-icon-div">{iconElem}</div>
<div className="model-slot-tile-dscription">{name}</div>
</div>
<div className="model-slot-tile-dscription">
{name}
</div>
</div >
)
}).filter(x => x != null)
}, [serverSetting.serverSetting.modelSlots, serverSetting.serverSetting.modelSlotIndex])
);
})
.filter((x) => x != null);
}, [serverSetting.serverSetting.modelSlots, serverSetting.serverSetting.modelSlotIndex, sortType]);
const modelSlotArea = useMemo(() => {
const onModelSlotEditClicked = () => {
guiState.stateControls.showModelSlotManagerCheckbox.updateState(true)
}
guiState.stateControls.showModelSlotManagerCheckbox.updateState(true);
};
const sortSlotByIdClass = sortType == "slot" ? "model-slot-sort-button-active" : "model-slot-sort-button";
const sortSlotByNameClass = sortType == "name" ? "model-slot-sort-button-active" : "model-slot-sort-button";
return (
<div className="model-slot-area">
<div className="model-slot-panel">
<div className="model-slot-tiles-container">{modelTiles}</div>
<div className="model-slot-buttons">
<div className="model-slot-sort-buttons">
<div
className={sortSlotByIdClass}
onClick={() => {
setSortType("slot");
}}
>
<FontAwesomeIcon icon={["fas", "arrow-down-1-9"]} style={{ fontSize: "1rem" }} />
</div>
<div
className={sortSlotByNameClass}
onClick={() => {
setSortType("name");
}}
>
<FontAwesomeIcon icon={["fas", "arrow-down-a-z"]} style={{ fontSize: "1rem" }} />
</div>
</div>
<div className="model-slot-button" onClick={onModelSlotEditClicked}>
{messageBuilderState.getMessage(__filename, "edit")}
</div>
</div>
</div>
</div>
)
}, [modelTiles])
);
}, [modelTiles, sortType]);
return modelSlotArea
}
if (webEdition) {
return <></>;
}
return modelSlotArea;
};

View File

@ -0,0 +1,211 @@
import React, { useEffect, useMemo, useState } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useMessageBuilder } from "../../../hooks/useMessageBuilder";
export type PortraitProps = {};
const BeatriceSpeakerType = {
male: "male",
female: "female",
} as const;
type BeatriceSpeakerType = (typeof BeatriceSpeakerType)[keyof typeof BeatriceSpeakerType];
// @ts-ignore
import MyIcon from "./female-clickable.svg";
import { useGuiState } from "../001_GuiStateProvider";
export const Portrait = (_props: PortraitProps) => {
const { serverSetting, volume, bufferingTime, performance, webInfoState, webEdition } = useAppState();
const messageBuilderState = useMessageBuilder();
const [beatriceSpeakerType, setBeatriceSpeakerType] = useState<BeatriceSpeakerType>(BeatriceSpeakerType.male);
const [beatriceSpeakerIndexInGender, setBeatriceSpeakerIndexInGender] = useState<string>("");
const { setBeatriceJVSSpeakerId } = useGuiState();
const beatriceMaleSpeakersList = [1, 3, 5, 6, 9, 11, 12, 13, 20, 21, 22, 23, 28, 31, 32, 33, 34, 37, 41, 42, 44, 45, 46, 47, 48, 49, 50, 52, 54, 68, 70, 71, 73, 74, 75, 76, 77, 78, 79, 80, 81, 86, 87, 88, 89, 97, 98, 99, 100];
const beatriceFemaleSpeakersList = [2, 4, 7, 8, 10, 14, 15, 16, 17, 18, 19, 24, 25, 26, 27, 29, 30, 35, 36, 38, 39, 40, 43, 51, 53, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 69, 72, 82, 83, 84, 85, 90, 91, 92, 93, 94, 95, 96];
useMemo(() => {
messageBuilderState.setMessage(__filename, "terms_of_use", { ja: "利用規約", en: "terms of use" });
}, []);
const selected = useMemo(() => {
if (webEdition) {
return webInfoState.webModelslot;
}
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots, webEdition]);
useEffect(() => {
const vol = document.getElementById("status-vol") as HTMLSpanElement;
const buf = document.getElementById("status-buf") as HTMLSpanElement;
const res = document.getElementById("status-res") as HTMLSpanElement;
const rtf = document.getElementById("status-rtf") as HTMLSpanElement;
if (!vol || !buf || !res) {
return;
}
vol.innerText = volume.toFixed(4);
if (webEdition) {
buf.innerText = bufferingTime.toString();
res.innerText = webInfoState.responseTimeInfo.responseTime.toString() ?? "0";
rtf.innerText = webInfoState.responseTimeInfo.rtf.toString() ?? "0";
} else {
buf.innerText = bufferingTime.toString();
res.innerText = performance.responseTime.toString();
}
}, [volume, bufferingTime, performance, webInfoState.responseTimeInfo]);
const setSelectedClass = () => {
const iframe = document.querySelector(".beatrice-speaker-graph-container");
if (!iframe) {
return;
}
// @ts-ignore
const svgDoc = iframe.contentDocument;
const gElements = svgDoc.getElementsByClassName("beatrice-node-pointer");
for (const gElement of gElements) {
gElement.classList.remove("beatrice-node-pointer-selected");
}
const keys = beatriceSpeakerIndexInGender.split("-");
const id = keys.pop();
const gender = keys.pop();
if (beatriceSpeakerType == gender) {
const selected = svgDoc.getElementById(`beatrice-node-${gender}-${id}`);
selected?.classList.add("beatrice-node-pointer-selected");
}
};
const setBeatriceSpeakerIndex = async (elementId: string) => {
setBeatriceSpeakerIndexInGender(elementId);
const keys = elementId.split("-");
const id = Number(keys.pop());
const gender = keys.pop();
let beatriceSpeakerIndex;
if (gender == "male") {
beatriceSpeakerIndex = beatriceMaleSpeakersList[id];
} else {
beatriceSpeakerIndex = beatriceFemaleSpeakersList[id];
}
setBeatriceJVSSpeakerId(beatriceSpeakerIndex);
};
useEffect(() => {
const iframe = document.querySelector(".beatrice-speaker-graph-container");
if (!iframe) {
return;
}
const setOnClick = () => {
// @ts-ignore
const svgDoc = iframe.contentDocument;
const gElements = svgDoc.getElementsByClassName("beatrice-node-pointer");
const textElements = svgDoc.getElementsByClassName("beatrice-text-pointer");
for (const gElement of gElements) {
gElement.onclick = () => {
setBeatriceSpeakerIndex(gElement.id);
};
}
for (const textElement of textElements) {
textElement.onclick = () => {
setBeatriceSpeakerIndex(textElement.id);
};
}
setSelectedClass();
};
iframe.addEventListener("load", setOnClick);
return () => {
iframe.removeEventListener("load", setOnClick);
};
}, [selected, beatriceSpeakerType]);
useEffect(() => {
setSelectedClass();
}, [selected, beatriceSpeakerType, beatriceSpeakerIndexInGender]);
const portrait = useMemo(() => {
if (!selected) {
return <></>;
}
let portrait;
if (webEdition) {
const icon = selected.iconFile;
portrait = <img className="portrait" src={icon} alt={selected.name} />;
} else if (selected.slotIndex == "Beatrice-JVS") {
const maleButtonClass = beatriceSpeakerType == "male" ? "button-selected" : "button";
const femaleButtonClass = beatriceSpeakerType == "male" ? "button" : "button-selected";
const svgURL = beatriceSpeakerType == "male" ? "./assets/beatrice/male-clickable.svg" : "./assets/beatrice/female-clickable.svg";
portrait = (
<>
<div className="beatrice-portrait-title">
Beatrice <span className="edition">JVS Corpus</span>
</div>
<div className="beatrice-portrait-select">
<div
className={maleButtonClass}
onClick={() => {
setBeatriceSpeakerType(BeatriceSpeakerType.male);
}}
>
male
</div>
<div
className={femaleButtonClass}
onClick={() => {
setBeatriceSpeakerType(BeatriceSpeakerType.female);
}}
>
female
</div>
</div>
{/* <iframe className="beatrice-speaker-graph-container" style={{ width: "20rem", height: "20rem", border: "none" }} src="./assets/beatrice/female-clickable.svg" title="terms_of_use" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"></iframe> */}
<iframe className="beatrice-speaker-graph-container" src={svgURL} title="beatrice JVS Corpus speakers" sandbox="allow-same-origin allow-scripts allow-popups allow-forms"></iframe>
</>
);
} else {
const modelDir = serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS" ? "model_dir_static" : serverSetting.serverSetting.voiceChangerParams.model_dir;
const icon = selected.iconFile.length > 0 ? modelDir + "/" + selected.slotIndex + "/" + selected.iconFile.split(/[\/\\]/).pop() : "./assets/icons/human.png";
portrait = <img className="portrait" src={icon} alt={selected.name} />;
}
const selectedTermOfUseUrlLink = selected.termsOfUseUrl ? (
<a href={selected.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="portrait-area-terms-of-use-link">
[{messageBuilderState.getMessage(__filename, "terms_of_use")}]
</a>
) : (
<></>
);
return (
<div className="portrait-area">
<div className="portrait-container">
{portrait}
<div className="portrait-area-status">
<p>
<span className="portrait-area-status-vctype">{selected.voiceChangerType}</span>
</p>
<p>
vol: <span id="status-vol">0</span>
</p>
<p>
buf: <span id="status-buf">0</span> ms
</p>
<p>
res: <span id="status-res">0</span> ms
</p>
<p>
rtf: <span id="status-rtf">0</span>
</p>
</div>
<div className="portrait-area-terms-of-use">{selectedTermOfUseUrlLink}</div>
</div>
</div>
);
}, [selected, beatriceSpeakerType]);
return portrait;
};

View File

@ -1,55 +1,103 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
export type TuningAreaProps = {
}
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useGuiState } from "../001_GuiStateProvider";
export type TuningAreaProps = {};
export const TuningArea = (_props: TuningAreaProps) => {
const { serverSetting } = useAppState()
const { serverSetting, webInfoState, webEdition } = useAppState();
const { setBeatriceJVSSpeakerPitch, beatriceJVSSpeakerPitch } = useGuiState();
const selected = useMemo(() => {
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return
if (webEdition) {
return webInfoState.webModelslot;
}
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex]
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots])
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots, webEdition]);
const tuningArea = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
if (selected.voiceChangerType == "MMVCv13" || selected.voiceChangerType == "MMVCv15") {
return <></>
return <></>;
}
const currentTuning = serverSetting.serverSetting.tran
const tranValueUpdatedAction = async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, tran: val })
// For Beatrice
if (selected.slotIndex == "Beatrice-JVS") {
const updateBeatriceJVSSpeakerPitch = async (pitch: number) => {
setBeatriceJVSSpeakerPitch(pitch);
};
return (
<div className="character-area-control">
<div className="character-area-control-title">TUNE:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input
type="range"
min="-2"
max="2"
step="1"
value={beatriceJVSSpeakerPitch}
onChange={(e) => {
updateBeatriceJVSSpeakerPitch(Number(e.target.value));
}}
></input>
</span>
<span className="character-area-slider-control-val">{beatriceJVSSpeakerPitch}</span>
</div>
</div>
</div>
);
}
let currentTuning;
if (webEdition) {
currentTuning = webInfoState.upkey;
} else {
currentTuning = serverSetting.serverSetting.tran;
}
const tranValueUpdatedAction = async (val: number) => {
if (webEdition) {
webInfoState.setUpkey(val);
} else {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, tran: val });
}
};
return (
<div className="character-area-control">
<div className="character-area-control-title">
TUNE:
</div>
<div className="character-area-control-title">TUNE:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input type="range" min="-50" max="50" step="1" value={currentTuning} onChange={(e) => {
tranValueUpdatedAction(Number(e.target.value))
}}></input>
<input
type="range"
min="-50"
max="50"
step="1"
value={currentTuning}
onChange={(e) => {
tranValueUpdatedAction(Number(e.target.value));
}}
></input>
</span>
<span className="character-area-slider-control-val">{currentTuning}</span>
</div>
</div>
</div>
)
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected])
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected, webEdition, webInfoState.upkey]);
return tuningArea
}
return tuningArea;
};

View File

@ -1,56 +1,59 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
export type IndexAreaProps = {
}
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
export type IndexAreaProps = {};
export const IndexArea = (_props: IndexAreaProps) => {
const { serverSetting } = useAppState()
const { serverSetting } = useAppState();
const selected = useMemo(() => {
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex]
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots])
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots]);
const indexArea = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
if (selected.voiceChangerType != "RVC") {
return <></>
return <></>;
}
const currentIndexRatio = serverSetting.serverSetting.indexRatio
const currentIndexRatio = serverSetting.serverSetting.indexRatio;
const indexRatioValueUpdatedAction = async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, indexRatio: val })
}
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, indexRatio: val });
};
return (
<div className="character-area-control">
<div className="character-area-control-title">
INDEX:
</div>
<div className="character-area-control-title">INDEX:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input type="range" min="0" max="1" step="0.1" value={currentIndexRatio} onChange={(e) => {
indexRatioValueUpdatedAction(Number(e.target.value))
}}></input>
<input
type="range"
min="0"
max="1"
step="0.1"
value={currentIndexRatio}
onChange={(e) => {
indexRatioValueUpdatedAction(Number(e.target.value));
}}
></input>
</span>
<span className="character-area-slider-control-val">{currentIndexRatio}</span>
</div>
</div>
</div>
)
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected])
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected]);
return indexArea
}
return indexArea;
};

View File

@ -1,113 +1,112 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
export type SpeakerAreaProps = {
}
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
export type SpeakerAreaProps = {};
export const SpeakerArea = (_props: SpeakerAreaProps) => {
const { serverSetting } = useAppState()
const { serverSetting } = useAppState();
const selected = useMemo(() => {
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex]
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots])
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots]);
const srcArea = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
if (selected.voiceChangerType != "MMVCv13" && selected.voiceChangerType != "MMVCv15") {
return <></>
return <></>;
}
const options = Object.keys(selected.speakers).map(key => {
const val = selected.speakers[Number(key)]
const options = Object.keys(selected.speakers).map((key) => {
const val = selected.speakers[Number(key)];
return (
<option key={key} value={key}>{val}[{key}]</option>
)
})
<option key={key} value={key}>
{val}[{key}]
</option>
);
});
const srcSpeakerValueUpdatedAction = async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, srcId: val })
}
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, srcId: val });
};
return (
<div className="character-area-control">
<div className="character-area-control-title">
Voice:
</div>
<div className="character-area-control-title">Voice:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind">src</span>
<span className="character-area-slider-control-slider">
<select value={serverSetting.serverSetting.srcId} onChange={(e) => { srcSpeakerValueUpdatedAction(Number(e.target.value)) }}>
<select
value={serverSetting.serverSetting.srcId}
onChange={(e) => {
srcSpeakerValueUpdatedAction(Number(e.target.value));
}}
>
{options}
</select>
</span>
</div>
</div>
</div >
)
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected])
</div>
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected]);
const dstArea = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
if (selected.slotIndex == "Beatrice-JVS") {
return; // beatrice JVS は変換先話者をグラフから選択するので、ここでは表示しない
}
const options = Object.keys(selected.speakers).map(key => {
const val = selected.speakers[Number(key)]
const options = Object.keys(selected.speakers).map((key) => {
const val = selected.speakers[Number(key)];
return (
<option key={key} value={key}>{val}[{key}]</option>
)
})
<option key={key} value={key}>
{val}[{key}]
</option>
);
});
const srcSpeakerValueUpdatedAction = async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, dstId: val })
}
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, dstId: val });
};
return (
<div className="character-area-control">
<div className="character-area-control-title">
{
selected.voiceChangerType == "DDSP-SVC" ||
selected.voiceChangerType == "so-vits-svc-40" ||
selected.voiceChangerType == "RVC" ? "Voice:" : ""
}
</div>
<div className="character-area-control-title">{selected.voiceChangerType == "DDSP-SVC" || selected.voiceChangerType == "so-vits-svc-40" || selected.voiceChangerType == "RVC" ? "Voice:" : ""}</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind">
{
selected.voiceChangerType == "MMVCv13" ||
selected.voiceChangerType == "MMVCv15" ? "dst" : ""
}
</span>
<span className="character-area-slider-control-kind">{selected.voiceChangerType == "MMVCv13" || selected.voiceChangerType == "MMVCv15" ? "dst" : ""}</span>
<span className="character-area-slider-control-slider">
<select value={serverSetting.serverSetting.dstId} onChange={(e) => { srcSpeakerValueUpdatedAction(Number(e.target.value)) }}>
<select
value={serverSetting.serverSetting.dstId}
onChange={(e) => {
srcSpeakerValueUpdatedAction(Number(e.target.value));
}}
>
{options}
</select>
</span>
</div>
</div>
</div >
)
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected])
</div>
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected]);
return (
<>
{srcArea}
{dstArea}
</>
)
}
);
};

View File

@ -1,72 +1,70 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
import { MMVCv15ModelSlot } from "@dannadori/voice-changer-client-js"
export type SpeakerAreaProps = {
}
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { MMVCv15ModelSlot } from "@dannadori/voice-changer-client-js";
export type SpeakerAreaProps = {};
export const F0FactorArea = (_props: SpeakerAreaProps) => {
const { serverSetting } = useAppState()
const { serverSetting } = useAppState();
const selected = useMemo(() => {
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex]
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots])
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots]);
const f0FactorArea = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
if (selected.voiceChangerType != "MMVCv15") {
return <></>
return <></>;
}
const selectedMMVCv15 = selected as MMVCv15ModelSlot
const selectedMMVCv15 = selected as MMVCv15ModelSlot;
const recommendF0 = (selectedMMVCv15.f0[serverSetting.serverSetting.dstId] / selectedMMVCv15.f0[serverSetting.serverSetting.srcId]).toFixed(2)
const recommendF0 = (selectedMMVCv15.f0[serverSetting.serverSetting.dstId] / selectedMMVCv15.f0[serverSetting.serverSetting.srcId]).toFixed(2);
return (
<>
<div className="character-area-control">
<div className="character-area-control-title">
F0Factor:
</div>
<div className="character-area-control-title">F0Factor:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input type="range" min="0.01" max="5.00" step="0.01" value={serverSetting.serverSetting.f0Factor} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, f0Factor: Number(e.target.value) })
}}></input>
<input
type="range"
min="0.01"
max="5.00"
step="0.01"
value={serverSetting.serverSetting.f0Factor}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, f0Factor: Number(e.target.value) });
}}
></input>
</span>
<span className="character-area-slider-control-val">{serverSetting.serverSetting.f0Factor}</span>
</div>
</div>
</div>
<div className="character-area-control">
<div className="character-area-control-title">
</div>
<div className="character-area-control-title"></div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-text">recommend:</span>
<span className="character-area-slider-control-text">
{recommendF0}
</span>
<span className="character-area-slider-control-text">{recommendF0}</span>
</div>
</div>
</div>
</>
)
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected])
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected]);
return f0FactorArea
}
return f0FactorArea;
};

View File

@ -1,81 +1,86 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
export type SoVitsSVC40SettingAreaProps = {
}
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
export type SoVitsSVC40SettingAreaProps = {};
export const SoVitsSVC40SettingArea = (_props: SoVitsSVC40SettingAreaProps) => {
const { serverSetting } = useAppState()
const { serverSetting } = useAppState();
const selected = useMemo(() => {
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex]
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots])
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots]);
const settingArea = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
if (selected.voiceChangerType != "so-vits-svc-40") {
return <></>
return <></>;
}
const cluster = (
<div className="character-area-control">
<div className="character-area-control-title">
Cluster:
</div>
<div className="character-area-control-title">Cluster:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input type="range" min="0" max="1.0" step="0.1" value={serverSetting.serverSetting.clusterInferRatio} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, clusterInferRatio: Number(e.target.value) })
}}></input>
<input
type="range"
min="0"
max="1.0"
step="0.1"
value={serverSetting.serverSetting.clusterInferRatio}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, clusterInferRatio: Number(e.target.value) });
}}
></input>
</span>
<span className="character-area-slider-control-val">{serverSetting.serverSetting.clusterInferRatio}</span>
</div>
</div>
</div>
)
);
const noise = (
<div className="character-area-control">
<div className="character-area-control-title">
Noise:
</div>
<div className="character-area-control-title">Noise:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input type="range" min="0" max="1.0" step="0.1" value={serverSetting.serverSetting.noiseScale} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, noiseScale: Number(e.target.value) })
}}></input>
<input
type="range"
min="0"
max="1.0"
step="0.1"
value={serverSetting.serverSetting.noiseScale}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, noiseScale: Number(e.target.value) });
}}
></input>
</span>
<span className="character-area-slider-control-val">{serverSetting.serverSetting.noiseScale}</span>
</div>
</div>
</div>
)
);
return (
<>
{cluster}
{noise}
</>
)
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected])
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected]);
return settingArea
}
return settingArea;
};

View File

@ -1,81 +1,86 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
export type DDSPSVC30SettingAreaProps = {
}
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
export type DDSPSVC30SettingAreaProps = {};
export const DDSPSVC30SettingArea = (_props: DDSPSVC30SettingAreaProps) => {
const { serverSetting } = useAppState()
const { serverSetting } = useAppState();
const selected = useMemo(() => {
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex]
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots])
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots]);
const settingArea = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
if (selected.voiceChangerType != "DDSP-SVC") {
return <></>
return <></>;
}
const acc = (
<div className="character-area-control">
<div className="character-area-control-title">
ACC:
</div>
<div className="character-area-control-title">ACC:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input type="range" min="1" max="20" step="1" value={serverSetting.serverSetting.diffAcc} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, diffAcc: Number(e.target.value) })
}}></input>
<input
type="range"
min="1"
max="20"
step="1"
value={serverSetting.serverSetting.diffAcc}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, diffAcc: Number(e.target.value) });
}}
></input>
</span>
<span className="character-area-slider-control-val">{serverSetting.serverSetting.diffAcc}</span>
</div>
</div>
</div>
)
);
const kstep = (
<div className="character-area-control">
<div className="character-area-control-title">
Kstep:
</div>
<div className="character-area-control-title">Kstep:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input type="range" min="21" max="300" step="1" value={serverSetting.serverSetting.kStep} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, kStep: Number(e.target.value) })
}}></input>
<input
type="range"
min="21"
max="300"
step="1"
value={serverSetting.serverSetting.kStep}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, kStep: Number(e.target.value) });
}}
></input>
</span>
<span className="character-area-slider-control-val">{serverSetting.serverSetting.kStep}</span>
</div>
</div>
</div>
)
);
return (
<>
{acc}
{kstep}
</>
)
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected])
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected]);
return settingArea
}
return settingArea;
};

View File

@ -0,0 +1,133 @@
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { DiffusionSVCModelSlot } from "@dannadori/voice-changer-client-js";
export type DiffusionSVCSettingAreaProps = {};
export const DiffusionSVCSettingArea = (_props: DiffusionSVCSettingAreaProps) => {
const { serverSetting } = useAppState();
const selected = useMemo(() => {
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots]);
const settingArea = useMemo(() => {
if (!selected) {
return <></>;
}
if (selected.voiceChangerType != "Diffusion-SVC") {
return <></>;
}
const skipDiffusionClass = serverSetting.serverSetting.skipDiffusion == 0 ? "character-area-toggle-button" : "character-area-toggle-button-active";
const skipDiffRow = (
<div className="character-area-control">
<div className="character-area-control-title">Boost</div>
<div className="character-area-control-field">
<div className="character-area-buttons">
<div
className={skipDiffusionClass}
onClick={() => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, skipDiffusion: serverSetting.serverSetting.skipDiffusion == 0 ? 1 : 0 });
}}
>
skip diff
</div>
</div>
</div>
</div>
);
const skipValues = getDivisors(serverSetting.serverSetting.kStep);
skipValues.pop();
const kStepRow = (
<div className="character-area-control">
<div className="character-area-control-title">k-step:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<input
type="range"
min="2"
max={(selected as DiffusionSVCModelSlot).kStepMax}
step="1"
value={serverSetting.serverSetting.kStep}
onChange={(e) => {
const newKStep = Number(e.target.value);
const newSkipValues = getDivisors(Number(e.target.value));
newSkipValues.pop();
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, speedUp: Math.max(...newSkipValues), kStep: newKStep });
}}
></input>
</span>
<span className="character-area-slider-control-val">{serverSetting.serverSetting.kStep}</span>
</div>
</div>
</div>
);
const speedUpRow = (
<div className="character-area-control">
<div className="character-area-control-title">skip</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-slider-control-slider">
<select
name=""
id=""
value={serverSetting.serverSetting.speedUp}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, speedUp: Number(e.target.value) });
}}
>
{skipValues.map((v) => {
return (
<option value={v} key={v}>
{v}
</option>
);
})}
</select>
</span>
</div>
</div>
</div>
);
return (
<>
{skipDiffRow}
{kStepRow}
{speedUpRow}
</>
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, selected]);
return settingArea;
};
const getDivisors = (num: number) => {
var divisors = [];
var end = Math.sqrt(num);
for (var i = 1; i <= end; i++) {
if (num % i === 0) {
divisors.push(i);
if (i !== num / i) {
divisors.push(num / i);
}
}
}
return divisors.sort((a, b) => a - b);
};

View File

@ -0,0 +1,199 @@
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useGuiState } from "../001_GuiStateProvider";
export type WebEditionSettingAreaProps = {};
export const WebEditionSettingArea = (_props: WebEditionSettingAreaProps) => {
const { serverSetting, webInfoState, webEdition } = useAppState();
const guiState = useGuiState();
const selected = useMemo(() => {
if (webEdition) {
return webInfoState.webModelslot;
}
return null;
}, [webEdition]);
const settingArea = useMemo(() => {
if (!selected) {
return <></>;
}
const readyForConfig = guiState.isConverting == false && webInfoState.webModelLoadingState == "ready";
const versionV1ClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.config.voiceChangerType == "rvcv1" ? " character-area-control-button-active" : " character-area-control-button-stanby");
const versionV2ClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.config.voiceChangerType == "rvcv2" ? " character-area-control-button-active" : " character-area-control-button-stanby");
const verison = (
<div className="character-area-control">
<div className="character-area-control-title">Version</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-control-buttons">
<span
className={!readyForConfig ? "character-area-control-button-disable" : versionV1ClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.config.voiceChangerType == "rvcv1" || !readyForConfig) return;
webInfoState.setVoiceChangerConfig("rvcv1", webInfoState.voiceChangerConfig.sampleRate, webInfoState.voiceChangerConfig.useF0, webInfoState.voiceChangerConfig.inputLength);
}}
>
v1
</span>
<span
className={!readyForConfig ? "character-area-control-button-disable" : versionV2ClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.config.voiceChangerType == "rvcv2" || !readyForConfig) return;
webInfoState.setVoiceChangerConfig("rvcv2", webInfoState.voiceChangerConfig.sampleRate, webInfoState.voiceChangerConfig.useF0, webInfoState.voiceChangerConfig.inputLength);
}}
>
v2
</span>
</span>
</div>
</div>
</div>
);
const sr16KClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.sampleRate == "16k" ? " character-area-control-button-active" : " character-area-control-button-stanby");
const sr32KClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.sampleRate == "32k" ? " character-area-control-button-active" : " character-area-control-button-stanby");
const sr40KClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.sampleRate == "40k" ? " character-area-control-button-active" : " character-area-control-button-stanby");
const sampleRate = (
<div className="character-area-control">
<div className="character-area-control-title">SR</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-control-buttons">
<span
className={!readyForConfig ? "character-area-control-button-disable" : sr16KClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.sampleRate == "16k" || !readyForConfig) return;
webInfoState.setVoiceChangerConfig("rvcv2", "16k", webInfoState.voiceChangerConfig.useF0, webInfoState.voiceChangerConfig.inputLength);
}}
>
16k
</span>
<span
className={!readyForConfig ? "character-area-control-button-disable" : sr32KClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.sampleRate == "32k" || !readyForConfig) return;
webInfoState.setVoiceChangerConfig(webInfoState.voiceChangerConfig.config.voiceChangerType, "32k", webInfoState.voiceChangerConfig.useF0, webInfoState.voiceChangerConfig.inputLength);
}}
>
32k
</span>
<span
className={!readyForConfig ? "character-area-control-button-disable" : sr40KClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.sampleRate == "40k" || !readyForConfig) return;
webInfoState.setVoiceChangerConfig(webInfoState.voiceChangerConfig.config.voiceChangerType, "40k", webInfoState.voiceChangerConfig.useF0, webInfoState.voiceChangerConfig.inputLength);
}}
>
40k
</span>
</span>
</div>
</div>
</div>
);
const pitchEnableClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.useF0 == true ? " character-area-control-button-active" : " character-area-control-button-stanby");
const pitchDisableClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.useF0 == false ? " character-area-control-button-active" : " character-area-control-button-stanby");
const pitch = (
<div className="character-area-control">
<div className="character-area-control-title">Pitch</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-control-buttons">
<span
className={!readyForConfig ? "character-area-control-button-disable" : pitchEnableClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.useF0 == true || !readyForConfig) return;
webInfoState.setVoiceChangerConfig(webInfoState.voiceChangerConfig.config.voiceChangerType, webInfoState.voiceChangerConfig.sampleRate, true, webInfoState.voiceChangerConfig.inputLength);
}}
>
Enable
</span>
<span
className={!readyForConfig ? "character-area-control-button-disable" : pitchDisableClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.useF0 == false || !readyForConfig) return;
webInfoState.setVoiceChangerConfig(webInfoState.voiceChangerConfig.config.voiceChangerType, webInfoState.voiceChangerConfig.sampleRate, false, webInfoState.voiceChangerConfig.inputLength);
}}
>
Disable
</span>
</span>
</div>
</div>
</div>
);
const latencyHighClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.inputLength == "24000" ? " character-area-control-button-active" : " character-area-control-button-stanby");
const latencyMidClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.inputLength == "12000" ? " character-area-control-button-active" : " character-area-control-button-stanby");
const latencyLowClassName = "character-area-control-button" + (webInfoState.voiceChangerConfig.inputLength == "8000" ? " character-area-control-button-active" : " character-area-control-button-stanby");
const latency = (
<div className="character-area-control">
<div className="character-area-control-title">Latency</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind"></span>
<span className="character-area-control-buttons">
<span
className={!readyForConfig ? "character-area-control-button-disable" : latencyHighClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.inputLength == "24000" || !readyForConfig) return;
webInfoState.setVoiceChangerConfig(webInfoState.voiceChangerConfig.config.voiceChangerType, webInfoState.voiceChangerConfig.sampleRate, webInfoState.voiceChangerConfig.useF0, "24000");
}}
>
High
</span>
<span
className={!readyForConfig ? "character-area-control-button-disable" : latencyMidClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.inputLength == "12000" || !readyForConfig) return;
webInfoState.setVoiceChangerConfig(webInfoState.voiceChangerConfig.config.voiceChangerType, webInfoState.voiceChangerConfig.sampleRate, webInfoState.voiceChangerConfig.useF0, "12000");
}}
>
Mid
</span>
<span
className={!readyForConfig ? "character-area-control-button-disable" : latencyLowClassName}
onClick={() => {
if (webInfoState.voiceChangerConfig.inputLength == "8000" || !readyForConfig) return;
webInfoState.setVoiceChangerConfig(webInfoState.voiceChangerConfig.config.voiceChangerType, webInfoState.voiceChangerConfig.sampleRate, webInfoState.voiceChangerConfig.useF0, "8000");
}}
>
Low
</span>
</span>
</div>
</div>
</div>
);
return (
<>
{verison}
{sampleRate}
{pitch}
{latency}
</>
);
}, [
serverSetting.serverSetting,
serverSetting.updateServerSettings,
selected,
webInfoState.upkey,
webInfoState.voiceChangerConfig.config.voiceChangerType,
webInfoState.voiceChangerConfig.sampleRate,
webInfoState.voiceChangerConfig.useF0,
webInfoState.voiceChangerConfig.inputLength,
webInfoState.webModelLoadingState,
guiState.isConverting,
webInfoState.webModelLoadingState,
]);
return settingArea;
};

View File

@ -1,88 +1,72 @@
import React, { useEffect, useMemo, useState } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
import { useGuiState } from "../001_GuiStateProvider"
import { OnnxExporterInfo } from "@dannadori/voice-changer-client-js"
import { useMessageBuilder } from "../../../hooks/useMessageBuilder"
import { TuningArea } from "./101-1_TuningArea"
import { IndexArea } from "./101-2_IndexArea"
import { SpeakerArea } from "./101-3_SpeakerArea"
import { F0FactorArea } from "./101-4_F0FactorArea"
import { SoVitsSVC40SettingArea } from "./101-5_so-vits-svc40SettingArea"
import { DDSPSVC30SettingArea } from "./101-6_ddsp-svc30SettingArea"
export type CharacterAreaProps = {
}
import React, { useEffect, useMemo, useState } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useGuiState } from "../001_GuiStateProvider";
import { OnnxExporterInfo } from "@dannadori/voice-changer-client-js";
import { useMessageBuilder } from "../../../hooks/useMessageBuilder";
import { TuningArea } from "./101-1_TuningArea";
import { IndexArea } from "./101-2_IndexArea";
import { SpeakerArea } from "./101-3_SpeakerArea";
import { F0FactorArea } from "./101-4_F0FactorArea";
import { SoVitsSVC40SettingArea } from "./101-5_so-vits-svc40SettingArea";
import { DDSPSVC30SettingArea } from "./101-6_ddsp-svc30SettingArea";
import { DiffusionSVCSettingArea } from "./101-7_diffusion-svcSettingArea";
import { Portrait } from "./101-0_Portrait";
import { useAppRoot } from "../../../001_provider/001_AppRootProvider";
import { WebEditionSettingArea } from "./101-8_web-editionSettingArea";
export type CharacterAreaProps = {};
export const CharacterArea = (_props: CharacterAreaProps) => {
const { serverSetting, initializedRef, volume, bufferingTime, performance, setting, setVoiceChangerClientSetting, start, stop } = useAppState()
const guiState = useGuiState()
const messageBuilderState = useMessageBuilder()
const { appGuiSettingState } = useAppRoot();
const { serverSetting, initializedRef, setting, setVoiceChangerClientSetting, start, stop, webInfoState } = useAppState();
const guiState = useGuiState();
const messageBuilderState = useMessageBuilder();
const webEdition = appGuiSettingState.edition.indexOf("web") >= 0;
const { beatriceJVSSpeakerId } = useGuiState();
useMemo(() => {
messageBuilderState.setMessage(__filename, "terms_of_use", { "ja": "利用規約", "en": "terms of use" })
messageBuilderState.setMessage(__filename, "export_to_onnx", { "ja": "onnx出力", "en": "export to onnx" })
messageBuilderState.setMessage(__filename, "save_default", { "ja": "設定保存", "en": "save setting" })
messageBuilderState.setMessage(__filename, "alert_onnx", { "ja": "ボイチェン中はonnx出力できません", "en": "cannot export onnx when voice conversion is enabled" })
}, [])
messageBuilderState.setMessage(__filename, "export_to_onnx", { ja: "onnx出力", en: "export to onnx" });
messageBuilderState.setMessage(__filename, "save_default", { ja: "設定保存", en: "save setting" });
messageBuilderState.setMessage(__filename, "alert_onnx", { ja: "ボイチェン中はonnx出力できません", en: "cannot export onnx when voice conversion is enabled" });
}, []);
const selected = useMemo(() => {
if (webEdition) {
return webInfoState.webModelslot;
}
if (serverSetting.serverSetting.modelSlotIndex == undefined) {
return
return;
} else if (serverSetting.serverSetting.modelSlotIndex == "Beatrice-JVS") {
const beatriceJVS = serverSetting.serverSetting.modelSlots.find((v) => v.slotIndex == "Beatrice-JVS");
return beatriceJVS;
} else {
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex];
}
return serverSetting.serverSetting.modelSlots[serverSetting.serverSetting.modelSlotIndex]
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots])
}, [serverSetting.serverSetting.modelSlotIndex, serverSetting.serverSetting.modelSlots, webEdition]);
const [startWithAudioContextCreate, setStartWithAudioContextCreate] = useState<boolean>(false);
useEffect(() => {
const vol = document.getElementById("status-vol") as HTMLSpanElement
const buf = document.getElementById("status-buf") as HTMLSpanElement
const res = document.getElementById("status-res") as HTMLSpanElement
if (!vol || !buf || !res) {
return
if (!startWithAudioContextCreate) {
return;
}
vol.innerText = volume.toFixed(4)
buf.innerText = bufferingTime.toString()
res.innerText = performance.responseTime.toString()
guiState.setIsConverting(true);
start();
}, [startWithAudioContextCreate]);
}, [volume, bufferingTime, performance])
const portrait = useMemo(() => {
const nameArea = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
const icon = selected.iconFile.length > 0 ? selected.iconFile : "./assets/icons/human.png"
const selectedTermOfUseUrlLink = selected.termsOfUseUrl ? <a href={selected.termsOfUseUrl} target="_blank" rel="noopener noreferrer" className="portrait-area-terms-of-use-link">[{messageBuilderState.getMessage(__filename, "terms_of_use")}]</a> : <></>
return (
<div className="portrait-area">
<div className="portrait-container">
<img className="portrait" src={icon} alt={selected.name} />
<div className="portrait-area-status">
<p><span className="portrait-area-status-vctype">{selected.voiceChangerType}</span></p>
<p>vol: <span id="status-vol">0</span></p>
<p>buf: <span id="status-buf">0</span> ms</p>
<p>res: <span id="status-res">0</span> ms</p>
</div>
<div className="portrait-area-terms-of-use">
{selectedTermOfUseUrlLink}
<div className="character-area-control">
<div className="character-area-control-title">Name:</div>
<div className="character-area-control-field">
<div className="character-area-text">
{selected.name} {selected.slotIndex == "Beatrice-JVS" ? `speaker:${beatriceJVSSpeakerId}` : ""}
</div>
</div>
</div>
)
}, [selected])
const [startWithAudioContextCreate, setStartWithAudioContextCreate] = useState<boolean>(false)
useEffect(() => {
if (!startWithAudioContextCreate) {
return
}
guiState.setIsConverting(true)
start()
}, [startWithAudioContextCreate])
);
}, [selected, beatriceJVSSpeakerId]);
const startControl = useMemo(() => {
const onStartClicked = async () => {
@ -90,82 +74,146 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
if (!initializedRef.current) {
while (true) {
await new Promise<void>((resolve) => {
setTimeout(resolve, 500)
})
setTimeout(resolve, 500);
});
if (initializedRef.current) {
break
break;
}
}
setStartWithAudioContextCreate(true)
setStartWithAudioContextCreate(true);
} else {
guiState.setIsConverting(true)
await start()
guiState.setIsConverting(true);
await start();
}
} else {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverAudioStated: 1 })
guiState.setIsConverting(true)
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverAudioStated: 1 });
guiState.setIsConverting(true);
}
}
};
const onStopClicked = async () => {
if (serverSetting.serverSetting.enableServerAudio == 0) {
guiState.setIsConverting(false)
await stop()
guiState.setIsConverting(false);
await stop();
} else {
guiState.setIsConverting(false)
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverAudioStated: 0 })
guiState.setIsConverting(false);
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverAudioStated: 0 });
}
};
const onPassThroughClicked = async () => {
if (serverSetting.serverSetting.passThrough == false) {
if (setting.voiceChangerClientSetting.passThroughConfirmationSkip) {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, passThrough: true });
guiState.stateControls.showEnablePassThroughDialogCheckbox.updateState(false);
} else {
guiState.stateControls.showEnablePassThroughDialogCheckbox.updateState(true);
}
} else {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, passThrough: false });
}
};
const startClassName = guiState.isConverting ? "character-area-control-button-active" : "character-area-control-button-stanby";
const stopClassName = guiState.isConverting ? "character-area-control-button-stanby" : "character-area-control-button-active";
const passThruClassName = serverSetting.serverSetting.passThrough == false ? "character-area-control-passthru-button-stanby" : "character-area-control-passthru-button-active blinking";
if (webEdition && webInfoState.webModelLoadingState != "ready") {
if (webInfoState.webModelLoadingState == "none" || webInfoState.webModelLoadingState == "loading") {
return (
<div className="character-area-control">
<div className="character-area-control-title">wait...</div>
<div className="character-area-control-field">
<div className="character-area-text blink">{webInfoState.webModelLoadingState}..</div>
<div className="character-area-text">
pre:{Math.floor(webInfoState.progressLoadPreprocess * 100)}%, model: {Math.floor(webInfoState.progressLoadVCModel * 100)}%
</div>
</div>
</div>
);
} else if (webInfoState.webModelLoadingState == "warmup") {
return (
<div className="character-area-control">
<div className="character-area-control-title">wait...</div>
<div className="character-area-control-field">
<div className="character-area-text blink">{webInfoState.webModelLoadingState}..</div>
<div className="character-area-text">warm up:{Math.floor(webInfoState.progressWarmup * 100)}%</div>
</div>
</div>
);
} else {
throw new Error("invalid webModelLoadingState");
}
} else {
if (webEdition) {
return (
<div className="character-area-control">
<div className="character-area-control-buttons">
<div onClick={onStartClicked} className={startClassName}>
start
</div>
<div onClick={onStopClicked} className={stopClassName}>
stop
</div>
</div>
</div>
);
} else {
return (
<div className="character-area-control">
<div className="character-area-control-buttons">
<div onClick={onStartClicked} className={startClassName}>
start
</div>
<div onClick={onStopClicked} className={stopClassName}>
stop
</div>
<div onClick={onPassThroughClicked} className={passThruClassName}>
passthru
</div>
</div>
</div>
);
}
}
const startClassName = guiState.isConverting ? "character-area-control-button-active" : "character-area-control-button-stanby"
const stopClassName = guiState.isConverting ? "character-area-control-button-stanby" : "character-area-control-button-active"
return (
<div className="character-area-control">
<div className="character-area-control-buttons">
<div onClick={onStartClicked} className={startClassName}>start</div>
<div onClick={onStopClicked} className={stopClassName}>stop</div>
</div>
</div>
)
}, [
guiState.isConverting,
start,
stop,
serverSetting.serverSetting,
serverSetting.updateServerSettings
])
}, [guiState.isConverting, start, stop, serverSetting.serverSetting, serverSetting.updateServerSettings, webInfoState.progressLoadPreprocess, webInfoState.progressLoadVCModel, webInfoState.progressWarmup, webInfoState.webModelLoadingState]);
const gainControl = useMemo(() => {
const currentInputGain = serverSetting.serverSetting.enableServerAudio == 0 ? setting.voiceChangerClientSetting.inputGain : serverSetting.serverSetting.serverInputAudioGain
const inputValueUpdatedAction = serverSetting.serverSetting.enableServerAudio == 0 ?
async (val: number) => {
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, inputGain: val })
} :
async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverInputAudioGain: val })
}
const currentOutputGain = serverSetting.serverSetting.enableServerAudio == 0 ? setting.voiceChangerClientSetting.outputGain : serverSetting.serverSetting.serverOutputAudioGain
const outputValueUpdatedAction = serverSetting.serverSetting.enableServerAudio == 0 ?
async (val: number) => {
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, outputGain: val })
} :
async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverOutputAudioGain: val })
}
const currentInputGain = serverSetting.serverSetting.enableServerAudio == 0 ? setting.voiceChangerClientSetting.inputGain : serverSetting.serverSetting.serverInputAudioGain;
const inputValueUpdatedAction =
serverSetting.serverSetting.enableServerAudio == 0
? async (val: number) => {
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, inputGain: val });
}
: async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverInputAudioGain: val });
};
const currentOutputGain = serverSetting.serverSetting.enableServerAudio == 0 ? setting.voiceChangerClientSetting.outputGain : serverSetting.serverSetting.serverOutputAudioGain;
const outputValueUpdatedAction =
serverSetting.serverSetting.enableServerAudio == 0
? async (val: number) => {
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, outputGain: val });
}
: async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverOutputAudioGain: val });
};
return (
<div className="character-area-control">
<div className="character-area-control-title">
GAIN:
</div>
<div className="character-area-control-title">GAIN:</div>
<div className="character-area-control-field">
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind">in</span>
<span className="character-area-slider-control-slider">
<input type="range" min="0.1" max="10.0" step="0.1" value={currentInputGain} onChange={(e) => {
inputValueUpdatedAction(Number(e.target.value))
}}></input>
<input
type="range"
min="0.1"
max="10.0"
step="0.1"
value={currentInputGain}
onChange={(e) => {
inputValueUpdatedAction(Number(e.target.value));
}}
></input>
</span>
<span className="character-area-slider-control-val">{currentInputGain}</span>
</div>
@ -173,70 +221,82 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
<div className="character-area-slider-control">
<span className="character-area-slider-control-kind">out</span>
<span className="character-area-slider-control-slider">
<input type="range" min="0.1" max="10.0" step="0.1" value={currentOutputGain} onChange={(e) => {
outputValueUpdatedAction(Number(e.target.value))
}}></input>
<input
type="range"
min="0.1"
max="10.0"
step="0.1"
value={currentOutputGain}
onChange={(e) => {
outputValueUpdatedAction(Number(e.target.value));
}}
></input>
</span>
<span className="character-area-slider-control-val">{currentOutputGain}</span>
</div>
</div>
</div>
)
}, [serverSetting.serverSetting, setting, setVoiceChangerClientSetting, serverSetting.updateServerSettings])
);
}, [serverSetting.serverSetting, setting, setVoiceChangerClientSetting, serverSetting.updateServerSettings]);
const modelSlotControl = useMemo(() => {
if (!selected) {
return <></>
return <></>;
}
if (webEdition) {
return <></>;
}
const onUpdateDefaultClicked = async () => {
await serverSetting.updateModelDefault()
}
await serverSetting.updateModelDefault();
};
const onnxExportButtonAction = async () => {
if (guiState.isConverting) {
alert(messageBuilderState.getMessage(__filename, "alert_onnx"))
return
alert(messageBuilderState.getMessage(__filename, "alert_onnx"));
return;
}
document.getElementById("dialog")?.classList.add("dialog-container-show")
guiState.stateControls.showWaitingCheckbox.updateState(true)
const res = await serverSetting.getOnnx() as OnnxExporterInfo
const a = document.createElement("a")
a.href = res.path
document.getElementById("dialog")?.classList.add("dialog-container-show");
guiState.stateControls.showWaitingCheckbox.updateState(true);
const res = (await serverSetting.getOnnx()) as OnnxExporterInfo;
const a = document.createElement("a");
a.href = res.path;
a.download = res.filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
guiState.stateControls.showWaitingCheckbox.updateState(false)
guiState.stateControls.showWaitingCheckbox.updateState(false);
};
}
const exportOnnx = selected.voiceChangerType == "RVC" && selected.modelFile.endsWith("pth") ? (
<div className="character-area-button" onClick={onnxExportButtonAction}>{messageBuilderState.getMessage(__filename, "export_to_onnx")}</div>
) : <></>
const exportOnnx =
selected.voiceChangerType == "RVC" && selected.modelFile.endsWith("pth") ? (
<div className="character-area-button" onClick={onnxExportButtonAction}>
{messageBuilderState.getMessage(__filename, "export_to_onnx")}
</div>
) : (
<></>
);
return (
<div className="character-area-control">
<div className="character-area-control-title">
</div>
<div className="character-area-control-title"></div>
<div className="character-area-control-field">
<div className="character-area-buttons">
<div className="character-area-button" onClick={onUpdateDefaultClicked}>{messageBuilderState.getMessage(__filename, "save_default")}</div>
<div className="character-area-button" onClick={onUpdateDefaultClicked}>
{messageBuilderState.getMessage(__filename, "save_default")}
</div>
{exportOnnx}
</div>
</div>
</div>
)
}, [selected, serverSetting.getOnnx, serverSetting.updateModelDefault])
);
}, [selected, serverSetting.getOnnx, serverSetting.updateModelDefault, guiState.isConverting]);
const characterArea = useMemo(() => {
return (
<div className="character-area">
{portrait}
<Portrait></Portrait>
<div className="character-area-control-area">
{nameArea}
{startControl}
{gainControl}
<TuningArea />
@ -245,11 +305,13 @@ export const CharacterArea = (_props: CharacterAreaProps) => {
<F0FactorArea />
<SoVitsSVC40SettingArea />
<DDSPSVC30SettingArea />
<DiffusionSVCSettingArea />
<WebEditionSettingArea />
{modelSlotControl}
</div>
</div>
)
}, [portrait, startControl, gainControl, modelSlotControl])
);
}, [startControl, gainControl, modelSlotControl]);
return characterArea
}
return characterArea;
};

View File

@ -1,17 +1,98 @@
import React, { useMemo } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
import { F0Detector, } from "@dannadori/voice-changer-client-js"
import React, { useMemo } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { F0Detector } from "@dannadori/voice-changer-client-js";
import { useAppRoot } from "../../../001_provider/001_AppRootProvider";
export type QualityAreaProps = {
detectors: string[]
}
detectors: string[];
};
export const QualityArea = (props: QualityAreaProps) => {
const { setVoiceChangerClientSetting, serverSetting, setting } = useAppState()
const { setVoiceChangerClientSetting, serverSetting, setting, webEdition } = useAppState();
const { appGuiSettingState } = useAppRoot();
const edition = appGuiSettingState.edition;
const qualityArea = useMemo(() => {
if (!serverSetting.updateServerSettings || !setVoiceChangerClientSetting || !serverSetting.serverSetting || !setting) {
return <></>
return <></>;
}
const generateF0DetOptions = () => {
if (edition.indexOf("onnxdirectML-cuda") >= 0) {
const recommended = ["crepe_tiny", "rmvpe_onnx"];
return Object.values(props.detectors).map((x) => {
if (recommended.includes(x)) {
return (
<option key={x} value={x}>
{x}
</option>
);
} else {
return (
<option key={x} value={x} disabled>
{x}(N/A)
</option>
);
}
});
} else {
return Object.values(props.detectors).map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
});
}
};
const f0DetOptions = generateF0DetOptions();
const f0Det = webEdition ? (
<></>
) : (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">F0 Det.:</div>
<div className="config-sub-area-control-field">
<select
className="body-select"
value={serverSetting.serverSetting.f0Detector}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, f0Detector: e.target.value as F0Detector });
}}
>
{f0DetOptions}
</select>
</div>
</div>
);
const threshold = webEdition ? (
<></>
) : (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">S.Thresh.:</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-slider-control">
<span className="config-sub-area-slider-control-kind"></span>
<span className="config-sub-area-slider-control-slider">
<input
type="range"
className="config-sub-area-slider-control-slider"
min="0.00000"
max="0.001"
step="0.00001"
value={serverSetting.serverSetting.silentThreshold || 0}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, silentThreshold: Number(e.target.value) });
}}
></input>
</span>
<span className="config-sub-area-slider-control-val">{serverSetting.serverSetting.silentThreshold}</span>
</div>
</div>
</div>
);
return (
<div className="config-sub-area">
<div className="config-sub-area-control">
@ -19,71 +100,58 @@ export const QualityArea = (props: QualityAreaProps) => {
<div className="config-sub-area-control-field">
<div className="config-sub-area-noise-container">
<div className="config-sub-area-noise-checkbox-container">
<input type="checkbox" disabled={serverSetting.serverSetting.enableServerAudio != 0} checked={setting.voiceChangerClientSetting.echoCancel} onChange={(e) => {
try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, echoCancel: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> <span>Echo</span>
<input
type="checkbox"
disabled={serverSetting.serverSetting.enableServerAudio != 0}
checked={setting.voiceChangerClientSetting.echoCancel}
onChange={(e) => {
try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, echoCancel: e.target.checked });
} catch (e) {
console.error(e);
}
}}
/>{" "}
<span>Echo</span>
</div>
<div className="config-sub-area-noise-checkbox-container">
<input type="checkbox" disabled={serverSetting.serverSetting.enableServerAudio != 0} checked={setting.voiceChangerClientSetting.noiseSuppression} onChange={(e) => {
try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, noiseSuppression: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> <span>Sup1</span>
<input
type="checkbox"
disabled={serverSetting.serverSetting.enableServerAudio != 0}
checked={setting.voiceChangerClientSetting.noiseSuppression}
onChange={(e) => {
try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, noiseSuppression: e.target.checked });
} catch (e) {
console.error(e);
}
}}
/>{" "}
<span>Sup1</span>
</div>
<div className="config-sub-area-noise-checkbox-container">
<input type="checkbox" disabled={serverSetting.serverSetting.enableServerAudio != 0} checked={setting.voiceChangerClientSetting.noiseSuppression2} onChange={(e) => {
try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, noiseSuppression2: e.target.checked })
} catch (e) {
console.error(e)
}
}} /> <span>Sup2</span>
<input
type="checkbox"
disabled={serverSetting.serverSetting.enableServerAudio != 0}
checked={setting.voiceChangerClientSetting.noiseSuppression2}
onChange={(e) => {
try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, noiseSuppression2: e.target.checked });
} catch (e) {
console.error(e);
}
}}
/>{" "}
<span>Sup2</span>
</div>
</div>
</div>
</div>
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">F0 Det.:</div>
<div className="config-sub-area-control-field">
<select className="body-select" value={serverSetting.serverSetting.f0Detector} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, f0Detector: e.target.value as F0Detector })
}}>
{
Object.values(props.detectors).map(x => {
//@ts-ignore
return <option key={x} value={x}>{x}</option>
})
}
</select>
</div>
</div>
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">S.Thresh.:</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-slider-control">
<span className="config-sub-area-slider-control-kind"></span>
<span className="config-sub-area-slider-control-slider">
<input type="range" className="config-sub-area-slider-control-slider" min="0.00000" max="0.001" step="0.00001" value={serverSetting.serverSetting.silentThreshold || 0} onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, silentThreshold: Number(e.target.value) })
}}></input>
</span>
<span className="config-sub-area-slider-control-val">{serverSetting.serverSetting.silentThreshold}</span>
</div>
</div>
</div>
{f0Det}
{threshold}
</div>
)
}, [serverSetting.serverSetting, setting, serverSetting.updateServerSettings, setVoiceChangerClientSetting])
);
}, [serverSetting.serverSetting, setting, serverSetting.updateServerSettings, setVoiceChangerClientSetting]);
return qualityArea
}
return qualityArea;
};

View File

@ -7,16 +7,22 @@ export type ConvertProps = {
};
export const ConvertArea = (props: ConvertProps) => {
const { setting, serverSetting, setWorkletNodeSetting, trancateBuffer } = useAppState();
const { setting, serverSetting, setWorkletNodeSetting, trancateBuffer, webEdition } = useAppState();
const { appGuiSettingState } = useAppRoot();
const edition = appGuiSettingState.edition;
const convertArea = useMemo(() => {
let nums: number[];
if (!props.inputChunkNums) {
nums = [8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048];
nums = [8, 16, 24, 32, 40, 48, 64, 80, 96, 112, 128, 192, 256, 320, 384, 448, 512, 576, 640, 704, 768, 832, 896, 960, 1024, 2048, 4096, 8192, 16384];
} else {
nums = props.inputChunkNums;
}
if (serverSetting.serverSetting.maxInputLength) {
nums = nums.filter((x) => {
return x < serverSetting.serverSetting.maxInputLength / 128;
});
}
const gpusEntry = [...serverSetting.serverSetting.gpus];
gpusEntry.push({
@ -25,40 +31,88 @@ export const ConvertArea = (props: ConvertProps) => {
memory: 0,
});
const onClassName = serverSetting.serverSetting.gpu == 0 ? "config-sub-area-button-active" : "config-sub-area-button";
const offClassName = serverSetting.serverSetting.gpu == 0 ? "config-sub-area-button" : "config-sub-area-button-active";
// const onClassName = serverSetting.serverSetting.gpu == 0 ? "config-sub-area-button-active" : "config-sub-area-button";
// const offClassName = serverSetting.serverSetting.gpu == 0 ? "config-sub-area-button" : "config-sub-area-button-active";
const cpuClassName = serverSetting.serverSetting.gpu == -1 ? "config-sub-area-button-active" : "config-sub-area-button";
const gpu0ClassName = serverSetting.serverSetting.gpu == 0 ? "config-sub-area-button-active" : "config-sub-area-button";
const gpu1ClassName = serverSetting.serverSetting.gpu == 1 ? "config-sub-area-button-active" : "config-sub-area-button";
const gpu2ClassName = serverSetting.serverSetting.gpu == 2 ? "config-sub-area-button-active" : "config-sub-area-button";
const gpu3ClassName = serverSetting.serverSetting.gpu == 3 ? "config-sub-area-button-active" : "config-sub-area-button";
const gpuSelect =
edition.indexOf("onnxdirectML-cuda") >= 0 ? (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">GPU(dml):</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-buttons">
<div
onClick={async () => {
await serverSetting.updateServerSettings({
...serverSetting.serverSetting,
gpu: 0,
});
}}
className={onClassName}
>
on
</div>
<div
onClick={async () => {
await serverSetting.updateServerSettings({
...serverSetting.serverSetting,
gpu: -1,
});
}}
className={offClassName}
>
off
<>
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">GPU(dml):</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-buttons">
<div
onClick={async () => {
await serverSetting.updateServerSettings({
...serverSetting.serverSetting,
gpu: -1,
});
}}
className={cpuClassName}
>
<span className="config-sub-area-button-text-small">cpu</span>
</div>
<div
onClick={async () => {
await serverSetting.updateServerSettings({
...serverSetting.serverSetting,
gpu: 0,
});
}}
className={gpu0ClassName}
>
<span className="config-sub-area-button-text-small">gpu0</span>
</div>
<div
onClick={async () => {
await serverSetting.updateServerSettings({
...serverSetting.serverSetting,
gpu: 1,
});
}}
className={gpu1ClassName}
>
<span className="config-sub-area-button-text-small">gpu1</span>
</div>
<div
onClick={async () => {
await serverSetting.updateServerSettings({
...serverSetting.serverSetting,
gpu: 2,
});
}}
className={gpu2ClassName}
>
<span className="config-sub-area-button-text-small">gpu2</span>
</div>
<div
onClick={async () => {
await serverSetting.updateServerSettings({
...serverSetting.serverSetting,
gpu: 3,
});
}}
className={gpu3ClassName}
>
<span className="config-sub-area-button-text-small">gpu3</span>
</div>
<div className="config-sub-area-control">
<span className="config-sub-area-button-text-small">
<a href="https://github.com/w-okada/voice-changer/issues/410">more info</a>
</span>
</div>
</div>
</div>
</div>
</div>
</>
) : webEdition ? (
<></>
) : (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">GPU:</div>
@ -82,6 +136,32 @@ export const ConvertArea = (props: ConvertProps) => {
</div>
</div>
);
const extraArea = webEdition ? (
<></>
) : (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">EXTRA:</div>
<div className="config-sub-area-control-field">
<select
className="body-select"
value={serverSetting.serverSetting.extraConvertSize}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, extraConvertSize: Number(e.target.value) });
trancateBuffer();
}}
>
{[1024 * 4, 1024 * 8, 1024 * 16, 1024 * 32, 1024 * 64, 1024 * 128].map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
})}
</select>
</div>
</div>
);
return (
<div className="config-sub-area">
<div className="config-sub-area-control">
@ -106,27 +186,7 @@ export const ConvertArea = (props: ConvertProps) => {
</select>
</div>
</div>
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">EXTRA:</div>
<div className="config-sub-area-control-field">
<select
className="body-select"
value={serverSetting.serverSetting.extraConvertSize}
onChange={(e) => {
serverSetting.updateServerSettings({ ...serverSetting.serverSetting, extraConvertSize: Number(e.target.value) });
trancateBuffer();
}}
>
{[1024 * 4, 1024 * 8, 1024 * 16, 1024 * 32, 1024 * 64, 1024 * 128].map((x) => {
return (
<option key={x} value={x}>
{x}
</option>
);
})}
</select>
</div>
</div>
{extraArea}
{gpuSelect}
</div>
);

View File

@ -2,23 +2,39 @@ import React, { useEffect, useMemo, useRef, useState } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { fileSelectorAsDataURL, useIndexedDB } from "@dannadori/voice-changer-client-js";
import { useGuiState } from "../001_GuiStateProvider";
import { AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../../const";
import { AUDIO_ELEMENT_FOR_PLAY_MONITOR, AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK, INDEXEDDB_KEY_AUDIO_MONITR, INDEXEDDB_KEY_AUDIO_OUTPUT } from "../../../const";
import { isDesktopApp } from "../../../const";
export type DeviceAreaProps = {};
export const DeviceArea = (_props: DeviceAreaProps) => {
const { setting, serverSetting, audioContext, setAudioOutputElementId, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording } = useAppState();
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, audioOutputForGUI, outputAudioDeviceInfo } = useGuiState();
const { setting, serverSetting, audioContext, setAudioOutputElementId, setAudioMonitorElementId, initializedRef, setVoiceChangerClientSetting, startOutputRecording, stopOutputRecording, webEdition } = useAppState();
const { isConverting, audioInputForGUI, inputAudioDeviceInfo, setAudioInputForGUI, fileInputEchoback, setFileInputEchoback, setAudioOutputForGUI, setAudioMonitorForGUI, audioOutputForGUI, audioMonitorForGUI, outputAudioDeviceInfo, shareScreenEnabled, setShareScreenEnabled, reloadDeviceInfo } = useGuiState();
const [inputHostApi, setInputHostApi] = useState<string>("ALL");
const [outputHostApi, setOutputHostApi] = useState<string>("ALL");
const [monitorHostApi, setMonitorHostApi] = useState<string>("ALL");
const audioSrcNode = useRef<MediaElementAudioSourceNode>();
const displayMediaStream = useRef<MediaStream | null>(null);
const { getItem, setItem } = useIndexedDB({ clientType: null });
const [outputRecordingStarted, setOutputRecordingStarted] = useState<boolean>(false);
// (1) Audio Mode
const deviceModeRow = useMemo(() => {
if (webEdition) {
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title">AUDIO:</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-buttons">
<div onClick={reloadDeviceInfo} className="config-sub-area-button">
reload
</div>
</div>
</div>
</div>
);
}
const enableServerAudio = serverSetting.serverSetting.enableServerAudio;
const clientChecked = enableServerAudio == 1 ? false : true;
const serverChecked = enableServerAudio == 1 ? true : false;
@ -61,6 +77,12 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
/>
<label htmlFor="server-device">server</label>
</div>
<div className="config-sub-area-buttons">
<div onClick={reloadDeviceInfo} className="config-sub-area-button">
reload
</div>
</div>
</div>
</div>
</div>
@ -97,7 +119,7 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
value={audioInputForGUI}
onChange={async (e) => {
setAudioInputForGUI(e.target.value);
if (e.target.value != "file") {
if (e.target.value != "file" && e.target.value != "screen") {
try {
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: e.target.value });
} catch (e) {
@ -132,7 +154,7 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
const hostAPIs = new Set(
devices.map((x) => {
return x.hostAPI;
})
}),
);
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => {
return (
@ -242,10 +264,10 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
audio_echo.volume = 0;
setFileInputEchoback(false);
// original stream to play.
const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement;
audio_org.src = url;
audio_org.pause();
// // original stream to play.
// const audio_org = document.getElementById(AUDIO_ELEMENT_FOR_TEST_ORIGINAL) as HTMLAudioElement;
// audio_org.src = url;
// audio_org.pause();
};
const echobackClass = fileInputEchoback ? "config-sub-area-control-field-wav-file-echoback-button-active" : "config-sub-area-control-field-wav-file-echoback-button";
@ -254,7 +276,7 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
<div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-wav-file left-padding-1">
<div className="config-sub-area-control-field-wav-file-audio-container">
<audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls hidden></audio>
{/* <audio id={AUDIO_ELEMENT_FOR_TEST_ORIGINAL} controls hidden></audio> */}
<audio className="config-sub-area-control-field-wav-file-audio" id={AUDIO_ELEMENT_FOR_TEST_CONVERTED} controls controlsList="nodownload noplaybackrate"></audio>
<audio id={AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK} controls hidden></audio>
</div>
@ -273,7 +295,96 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
</div>
</div>
);
}, [audioInputForGUI, fileInputEchoback, serverSetting.serverSetting]);
}, [audioInputForGUI, fileInputEchoback, setting, serverSetting.serverSetting]);
const audioInputScreenRow = useMemo(() => {
if (audioInputForGUI != "screen" || serverSetting.serverSetting.enableServerAudio == 1) {
return <></>;
}
const onSelectScreenClicked = async () => {
// 既存msをクローズ
if (displayMediaStream.current) {
displayMediaStream.current.getTracks().forEach((x) => {
x.stop();
});
displayMediaStream.current = null;
}
// 共有ストップ
if (shareScreenEnabled == true) {
setShareScreenEnabled(false);
return;
}
// 共有スタート
try {
if (isDesktopApp()) {
const constraints = {
audio: {
mandatory: {
chromeMediaSource: "desktop",
},
},
video: {
mandatory: {
chromeMediaSource: "desktop",
},
},
};
// @ts-ignore
displayMediaStream.current = await navigator.mediaDevices.getUserMedia(constraints);
} else {
displayMediaStream.current = await navigator.mediaDevices.getDisplayMedia({
video: true,
audio: true,
});
}
} catch (e) {
console.log(e);
return;
}
if (!displayMediaStream.current) {
console.log("no ms");
return;
}
if (displayMediaStream.current?.getAudioTracks().length == 0) {
displayMediaStream.current.getTracks().forEach((x) => {
x.stop();
});
displayMediaStream.current = null;
console.log("no audio track");
return;
}
try {
setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, audioInput: displayMediaStream.current });
} catch (e) {
console.error(e);
}
setShareScreenEnabled(!shareScreenEnabled);
};
const echobackClass = shareScreenEnabled ? "config-sub-area-control-field-screen-select-button-active" : "config-sub-area-control-field-screen-select-button";
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1"></div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-screen-select">
<div
className={echobackClass}
onClick={() => {
onSelectScreenClicked();
}}
>
capture
</div>
</div>
</div>
</div>
);
}, [audioInputForGUI, setting, serverSetting.serverSetting, shareScreenEnabled, setShareScreenEnabled]);
// (3) Audio Output
useEffect(() => {
@ -290,15 +401,20 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
const setAudioOutput = async () => {
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
// [AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_ORIGINAL, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
[AUDIO_ELEMENT_FOR_PLAY_RESULT, AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK].forEach((x) => {
const audio = document.getElementById(x) as HTMLAudioElement;
if (audio) {
if (serverSetting.serverSetting.enableServerAudio == 1) {
// Server Audio を使う場合はElementから音は出さない。
audio.volume = 0;
} else if (audioOutputForGUI == "none") {
// @ts-ignore
audio.setSinkId("");
try {
// @ts-ignore
audio.setSinkId("");
} catch (e) {
console.error("catch:" + e);
}
if (x == AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK) {
audio.volume = 0;
} else {
@ -312,8 +428,12 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
return x.deviceId == audioOutputForGUI;
});
if (found) {
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。
audio.setSinkId(audioOutputForGUI);
try {
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。
audio.setSinkId(audioOutputForGUI);
} catch (e) {
console.error("catch:" + e);
}
} else {
console.warn("No audio output device. use default");
}
@ -375,7 +495,7 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
const hostAPIs = new Set(
devices.map((x) => {
return x.hostAPI;
})
}),
);
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => {
return (
@ -507,7 +627,96 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
);
}, [serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
// (6) Monitor
// (6) モニター
useEffect(() => {
const loadCache = async () => {
const key = await getItem(INDEXEDDB_KEY_AUDIO_MONITR);
if (key) {
setAudioMonitorForGUI(key as string);
}
};
loadCache();
}, []);
useEffect(() => {
const setAudioMonitor = async () => {
const mediaDeviceInfos = await navigator.mediaDevices.enumerateDevices();
[AUDIO_ELEMENT_FOR_PLAY_MONITOR].forEach((x) => {
const audio = document.getElementById(x) as HTMLAudioElement;
if (audio) {
if (serverSetting.serverSetting.enableServerAudio == 1) {
// Server Audio を使う場合はElementから音は出さない。
audio.volume = 0;
} else if (audioMonitorForGUI == "none") {
try {
// @ts-ignore
audio.setSinkId("");
audio.volume = 0;
} catch (e) {
console.error("catch:" + e);
}
} else {
const audioOutputs = mediaDeviceInfos.filter((x) => {
return x.kind == "audiooutput";
});
const found = audioOutputs.some((x) => {
return x.deviceId == audioMonitorForGUI;
});
if (found) {
try {
// @ts-ignore // 例外キャッチできないので事前にIDチェックが必要らしい。
audio.setSinkId(audioMonitorForGUI);
audio.volume = 1;
} catch (e) {
console.error("catch:" + e);
}
} else {
console.warn("No audio output device. use default");
}
}
}
});
};
setAudioMonitor();
}, [audioMonitorForGUI, serverSetting.serverSetting.enableServerAudio]);
// (6-1) クライアント
const clientMonitorRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 1) {
return <></>;
}
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-1">monitor</div>
<div className="config-sub-area-control-field">
<select
className="body-select"
value={audioMonitorForGUI}
onChange={(e) => {
setAudioMonitorForGUI(e.target.value);
setItem(INDEXEDDB_KEY_AUDIO_MONITR, e.target.value);
}}
>
{outputAudioDeviceInfo.map((x) => {
return (
<option key={x.deviceId} value={x.deviceId}>
{x.label}
</option>
);
})}
</select>
</div>
</div>
);
}, [serverSetting.serverSetting.enableServerAudio, outputAudioDeviceInfo, audioMonitorForGUI]);
useEffect(() => {
console.log("initializedRef.current", initializedRef.current);
setAudioMonitorElementId(AUDIO_ELEMENT_FOR_PLAY_MONITOR);
}, [initializedRef.current]);
// (6-2) サーバ
const serverMonitorRow = useMemo(() => {
if (serverSetting.serverSetting.enableServerAudio == 0) {
return <></>;
@ -516,7 +725,7 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
const hostAPIs = new Set(
devices.map((x) => {
return x.hostAPI;
})
}),
);
const hostAPIOptions = Array.from(hostAPIs).map((x, index) => {
return (
@ -541,7 +750,7 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
filteredDevice.unshift(
<option value={-1} key={-1}>
none
</option>
</option>,
);
const currentValue = devices.find((x) => {
@ -584,6 +793,41 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
);
}, [monitorHostApi, serverSetting.serverSetting, serverSetting.updateServerSettings, serverSetting.serverSetting.enableServerAudio]);
const monitorGainControl = useMemo(() => {
const currentMonitorGain = serverSetting.serverSetting.enableServerAudio == 0 ? setting.voiceChangerClientSetting.monitorGain : serverSetting.serverSetting.serverMonitorAudioGain;
const monitorValueUpdatedAction =
serverSetting.serverSetting.enableServerAudio == 0
? async (val: number) => {
await setVoiceChangerClientSetting({ ...setting.voiceChangerClientSetting, monitorGain: val });
}
: async (val: number) => {
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, serverMonitorAudioGain: val });
};
return (
<div className="config-sub-area-control">
<div className="config-sub-area-control-title left-padding-2">gain</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-auido-io">
<span className="character-area-slider-control-slider">
<input
type="range"
min="0.1"
max="10.0"
step="0.1"
value={currentMonitorGain}
onChange={(e) => {
monitorValueUpdatedAction(Number(e.target.value));
}}
></input>
</span>
<span className="character-area-slider-control-val">{currentMonitorGain}</span>
</div>
</div>
</div>
);
}, [serverSetting.serverSetting, setting, setVoiceChangerClientSetting, serverSetting.updateServerSettings]);
return (
<div className="config-sub-area">
{deviceModeRow}
@ -591,12 +835,16 @@ export const DeviceArea = (_props: DeviceAreaProps) => {
{clientAudioInputRow}
{serverAudioInputRow}
{audioInputMediaRow}
{audioInputScreenRow}
{clientAudioOutputRow}
{serverAudioOutputRow}
{clientMonitorRow}
{serverMonitorRow}
{monitorGainControl}
{outputRecorderRow}
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_RESULT}></audio>
<audio hidden id={AUDIO_ELEMENT_FOR_PLAY_MONITOR}></audio>
</div>
);
};

View File

@ -1,44 +1,52 @@
import React, { useMemo, useState } from "react"
import { useAppState } from "../../../001_provider/001_AppStateProvider"
import { useGuiState } from "../001_GuiStateProvider"
import { AUDIO_ELEMENT_FOR_SAMPLING_INPUT, AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT } from "../../../const"
import React, { useMemo, useState } from "react";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
import { useGuiState } from "../001_GuiStateProvider";
import { AUDIO_ELEMENT_FOR_SAMPLING_INPUT, AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT } from "../../../const";
export type RecorderAreaProps = {
}
export type RecorderAreaProps = {};
export const RecorderArea = (_props: RecorderAreaProps) => {
const { serverSetting } = useAppState()
const { audioOutputForAnalyzer, setAudioOutputForAnalyzer, outputAudioDeviceInfo } = useGuiState()
const [serverIORecording, setServerIORecording] = useState<boolean>(false)
const { serverSetting, webEdition } = useAppState();
const { audioOutputForAnalyzer, setAudioOutputForAnalyzer, outputAudioDeviceInfo } = useGuiState();
const [serverIORecording, setServerIORecording] = useState<boolean>(false);
const serverIORecorderRow = useMemo(() => {
const onServerIORecordStartClicked = async () => {
setServerIORecording(true)
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, recordIO: 1 })
if (webEdition) {
return <> </>;
}
const onServerIORecordStartClicked = async () => {
setServerIORecording(true);
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, recordIO: 1 });
};
const onServerIORecordStopClicked = async () => {
setServerIORecording(false)
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, recordIO: 0 })
setServerIORecording(false);
await serverSetting.updateServerSettings({ ...serverSetting.serverSetting, recordIO: 0 });
// set wav (input)
const wavInput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_INPUT) as HTMLAudioElement
wavInput.src = "/tmp/in.wav?" + new Date().getTime()
wavInput.controls = true
// @ts-ignore
wavInput.setSinkId(audioOutputForAnalyzer)
const wavInput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_INPUT) as HTMLAudioElement;
wavInput.src = "/tmp/in.wav?" + new Date().getTime();
wavInput.controls = true;
try {
// @ts-ignore
wavInput.setSinkId(audioOutputForAnalyzer);
} catch (e) {
console.log(e);
}
// set wav (output)
const wavOutput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT) as HTMLAudioElement
wavOutput.src = "/tmp/out.wav?" + new Date().getTime()
wavOutput.controls = true
// @ts-ignore
wavOutput.setSinkId(audioOutputForAnalyzer)
}
const wavOutput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT) as HTMLAudioElement;
wavOutput.src = "/tmp/out.wav?" + new Date().getTime();
wavOutput.controls = true;
try {
// @ts-ignore
wavOutput.setSinkId(audioOutputForAnalyzer);
} catch (e) {
console.log(e);
}
};
const startClassName = serverIORecording ? "config-sub-area-button-active" : "config-sub-area-button"
const stopClassName = serverIORecording ? "config-sub-area-button" : "config-sub-area-button-active"
const startClassName = serverIORecording ? "config-sub-area-button-active" : "config-sub-area-button";
const stopClassName = serverIORecording ? "config-sub-area-button" : "config-sub-area-button-active";
return (
<>
<div className="config-sub-area-control">
@ -49,34 +57,51 @@ export const RecorderArea = (_props: RecorderAreaProps) => {
<div className="config-sub-area-control-title">SIO rec.</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-buttons">
<div onClick={onServerIORecordStartClicked} className={startClassName}>start</div>
<div onClick={onServerIORecordStopClicked} className={stopClassName}>stop</div>
<div onClick={onServerIORecordStartClicked} className={startClassName}>
start
</div>
<div onClick={onServerIORecordStopClicked} className={stopClassName}>
stop
</div>
</div>
</div>
</div>
<div className="config-sub-area-control left-padding-1">
<div className="config-sub-area-control-title">output</div>
<div className="config-sub-area-control-field">
<div className="config-sub-area-control-field-auido-io">
<select className="body-select" value={audioOutputForAnalyzer} onChange={(e) => {
setAudioOutputForAnalyzer(e.target.value)
const wavInput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_INPUT) as HTMLAudioElement
const wavOutput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT) as HTMLAudioElement
//@ts-ignore
wavInput.setSinkId(e.target.value)
//@ts-ignore
wavOutput.setSinkId(e.target.value)
}}>
{
outputAudioDeviceInfo.map(x => {
<select
className="body-select"
value={audioOutputForAnalyzer}
onChange={(e) => {
setAudioOutputForAnalyzer(e.target.value);
const wavInput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_INPUT) as HTMLAudioElement;
const wavOutput = document.getElementById(AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT) as HTMLAudioElement;
try {
//@ts-ignore
wavInput.setSinkId(e.target.value);
//@ts-ignore
wavOutput.setSinkId(e.target.value);
} catch (e) {
console.log(e);
}
}}
>
{outputAudioDeviceInfo
.map((x) => {
if (x.deviceId == "none") {
return null
return null;
}
return <option key={x.deviceId} value={x.deviceId}>{x.label}</option>
}).filter(x => { return x != null })
}
return (
<option key={x.deviceId} value={x.deviceId}>
{x.label}
</option>
);
})
.filter((x) => {
return x != null;
})}
</select>
</div>
</div>
@ -102,17 +127,9 @@ export const RecorderArea = (_props: RecorderAreaProps) => {
</div>
</div>
</div>
</>
)
}, [serverIORecording, audioOutputForAnalyzer, outputAudioDeviceInfo, serverSetting.updateServerSettings])
return (
<div className="config-sub-area">
{serverIORecorderRow}
</div>
)
}
);
}, [serverIORecording, audioOutputForAnalyzer, outputAudioDeviceInfo, serverSetting.updateServerSettings]);
return <div className="config-sub-area">{serverIORecorderRow}</div>;
};

View File

@ -1,10 +1,12 @@
import React, { useMemo } from "react";
import { useGuiState } from "../001_GuiStateProvider";
import { useAppState } from "../../../001_provider/001_AppStateProvider";
export type MoreActionAreaProps = {};
export const MoreActionArea = (_props: MoreActionAreaProps) => {
const { stateControls } = useGuiState();
const { webEdition } = useAppState();
const serverIORecorderRow = useMemo(() => {
const onOpenMergeLabClicked = () => {
@ -44,5 +46,9 @@ export const MoreActionArea = (_props: MoreActionAreaProps) => {
);
}, [stateControls]);
return <div className="config-sub-area">{serverIORecorderRow}</div>;
if (webEdition) {
return <> </>;
} else {
return <div className="config-sub-area">{serverIORecorderRow}</div>;
}
};

View File

@ -1,15 +1,18 @@
export const AUDIO_ELEMENT_FOR_PLAY_RESULT = "audio-result"
export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original"
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted"
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback"
export const AUDIO_ELEMENT_FOR_PLAY_RESULT = "audio-result" // 変換後の出力用プレイヤー
export const AUDIO_ELEMENT_FOR_PLAY_MONITOR = "audio-monitor" // 変換後のモニター用プレイヤー
export const AUDIO_ELEMENT_FOR_TEST_ORIGINAL = "audio-test-original" // ??? 使ってないかも。
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED = "audio-test-converted" // ファイルインプットのコントロール
export const AUDIO_ELEMENT_FOR_TEST_CONVERTED_ECHOBACK = "audio-test-converted-echoback" // ファイルインプットのエコーバック
export const AUDIO_ELEMENT_FOR_SAMPLING_INPUT = "body-wav-container-wav-input"
export const AUDIO_ELEMENT_FOR_SAMPLING_OUTPUT = "body-wav-container-wav-output"
export const INDEXEDDB_KEY_AUDIO_OUTPUT = "INDEXEDDB_KEY_AUDIO_OUTPUT"
export const INDEXEDDB_KEY_AUDIO_MONITR = "INDEXEDDB_KEY_AUDIO_MONITOR"
export const INDEXEDDB_KEY_DEFAULT_MODEL_TYPE = "INDEXEDDB_KEY_DEFALT_MODEL_TYPE"
export const MODEL_ICON_BLANK_URL = "/assets/icons/blank.png"
export const isDesktopApp = () => {
if (navigator.userAgent.indexOf('Electron') >= 0) {

View File

@ -757,6 +757,18 @@ body {
max-height: 60vh;
width: 100%;
overflow-y: scroll;
&::-webkit-scrollbar {
width: 10px;
height: 10px;
}
&::-webkit-scrollbar-track {
background-color: #eee;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #f7cfec80;
border-radius: 3px;
}
.model-slot {
height: 5rem;
@ -883,10 +895,10 @@ body {
flex-direction: row;
margin: 0.2rem;
.file-uploader-file-select-row-label {
width: 5rem;
width: 10rem;
}
.file-uploader-file-select-row-value {
width: 20rem;
width: 15rem;
color: #f00;
white-space: nowrap;
}
@ -1112,6 +1124,7 @@ body {
position: relative;
cursor: pointer;
display: inline-block;
z-index: 10;
}
/* ################## */
@ -1150,12 +1163,30 @@ body {
flex-direction: row;
gap: 2px;
flex-wrap: wrap;
overflow-y: scroll;
max-height: 12rem;
&::-webkit-scrollbar {
width: 10px;
height: 10px;
}
&::-webkit-scrollbar-track {
background-color: #eee;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #f7cfec80;
border-radius: 3px;
}
/* width: calc(30rem + 40px + 10px); */
}
.model-slot-buttons {
display: flex;
flex-direction: column-reverse;
gap: 5px;
flex-direction: column;
justify-content: space-between;
width: 4rem;
.model-slot-button {
border: solid 2px #999;
color: white;
@ -1164,10 +1195,41 @@ body {
background: #333;
cursor: pointer;
padding: 5px;
text-align: center;
width: 3rem;
}
.model-slot-button:hover {
border: solid 2px #faa;
}
.model-slot-sort-buttons {
height: 50%;
.model-slot-sort-button {
color: white;
font-size: 0.8rem;
border-radius: 4px;
background: #333;
border: solid 2px #444;
cursor: pointer;
padding: 1px;
text-align: center;
width: 3rem;
}
.model-slot-sort-button-active {
color: white;
font-size: 0.8rem;
border-radius: 4px;
background: #595;
border: solid 2px #595;
cursor: pointer;
padding: 1px;
text-align: center;
width: 3rem;
}
.model-slot-sort-button:hover {
border: solid 2px #faa;
background: #343;
}
}
}
}
}
@ -1260,7 +1322,7 @@ body {
background: rgba(100, 100, 100, 0.5);
color: white;
position: absolute;
paddig: 2px;
padding: 2px;
font-size: 0.7rem;
right: 5px;
bottom: 5px;
@ -1277,6 +1339,7 @@ body {
.character-area-control {
display: flex;
gap: 3px;
align-items: center;
.character-area-control-buttons {
display: flex;
flex-direction: row;
@ -1301,6 +1364,43 @@ body {
border: solid 1px #000;
}
}
.character-area-control-button-disable {
width: 5rem;
border: solid 1px #333;
border-radius: 2px;
background: #d3d7d3;
font-weight: 700;
text-align: center;
color: grey;
}
.character-area-control-passthru-button-stanby {
width: 5rem;
border: solid 1px #999;
border-radius: 15px;
padding: 2px;
background: #aba;
cursor: pointer;
font-weight: 700;
font-size: 0.8rem;
text-align: center;
&:hover {
border: solid 1px #000;
}
}
.character-area-control-passthru-button-active {
width: 5rem;
border: solid 1px #955;
border-radius: 15px;
padding: 2px;
background: #fdd;
cursor: pointer;
font-weight: 700;
font-size: 0.8rem;
text-align: center;
&:hover {
border: solid 1px #000;
}
}
}
.character-area-control-title {
@ -1312,6 +1412,9 @@ body {
display: flex;
flex-direction: column;
.character-area-text {
font-size: 0.9rem;
}
.character-area-slider-control {
display: flex;
flex-direction: row;
@ -1344,6 +1447,35 @@ body {
.character-area-button:hover {
border: solid 2px #faa;
}
.character-area-toggle-button {
border: solid 2px #999;
color: white;
background: #666;
cursor: pointer;
font-size: 0.8rem;
border-radius: 5px;
height: 1.2rem;
padding-left: 2px;
padding-right: 2px;
}
.character-area-toggle-button:hover {
border: solid 2px #faa;
}
.character-area-toggle-button-active {
border: solid 2px #999;
color: white;
background: #844;
cursor: pointer;
font-size: 0.8rem;
border-radius: 5px;
height: 1.2rem;
padding-left: 2px;
padding-right: 2px;
}
}
}
}
@ -1443,6 +1575,10 @@ audio::-webkit-media-controls-overlay-enclosure{
height: 1.2rem;
padding-left: 2px;
padding-right: 2px;
white-space: nowrap;
}
.config-sub-area-button-text-small {
font-size: 0.5rem;
}
}
.config-sub-area-control-field-auido-io {
@ -1455,6 +1591,31 @@ audio::-webkit-media-controls-overlay-enclosure{
max-width: 70%;
}
}
.config-sub-area-control-field-screen-select {
display: flex;
flex-direction: row-reverse;
gap: 5px;
.config-sub-area-control-field-screen-select-button-active {
font-size: 0.8rem;
border: solid 1px #333;
border-radius: 5px;
background: #ada;
height: 1.2rem;
padding-left: 2px;
padding-right: 2px;
cursor: pointer;
}
.config-sub-area-control-field-screen-select-button {
border: solid 1px #333;
background: #fff;
font-size: 0.8rem;
border-radius: 5px;
height: 1.2rem;
padding-left: 2px;
padding-right: 2px;
cursor: pointer;
}
}
.config-sub-area-control-field-wav-file {
display: flex;
flex-direction: row;
@ -1561,6 +1722,11 @@ audio::-webkit-media-controls-overlay-enclosure{
font-weight: 700;
font-size: 0.9rem;
}
.advanced-setting-container-row-title-long {
width: 20rem;
font-weight: 700;
font-size: 0.9rem;
}
.advanced-setting-container-row-field {
width: 15rem;
font-size: 0.9rem;
@ -1610,6 +1776,21 @@ audio::-webkit-media-controls-overlay-enclosure{
flex-direction: row;
.merge-lab-model-list {
width: 70%;
overflow-y: scroll;
max-height: 20rem;
&::-webkit-scrollbar {
width: 10px;
height: 10px;
}
&::-webkit-scrollbar-track {
background-color: #eee;
border-radius: 3px;
}
&::-webkit-scrollbar-thumb {
background: #f7cfec80;
border-radius: 3px;
}
.merge-lab-model-item {
display: flex;
flex-direction: row;
@ -1648,3 +1829,77 @@ audio::-webkit-media-controls-overlay-enclosure{
}
}
}
.blinking {
animation: flash 0.7s cubic-bezier(0.91, -0.14, 0, 1.4) infinite;
}
@keyframes flash {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
.blink {
animation: blinking 0.8s ease-in-out infinite alternate;
}
@keyframes blinking {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.beatrice-portrait-title {
font-size: 1rem;
font-weight: 700;
color: #333;
text-shadow: 0 0 2px #333;
text-align: center;
.edition {
font-size: 0.6rem;
}
}
.beatrice-portrait-select {
display: flex;
justify-content: center;
.button {
/* border: solid 2px #999; */
color: #615454;
font-weight: 700;
font-size: 0.8rem;
border-radius: 2px;
background: #adafad;
cursor: pointer;
padding: 0px 5px 0px 5px;
margin: 0px 5px 0px 5px;
line-height: 140%;
height: 1.1rem;
}
.button-selected {
/* border: solid 2px #999; */
color: #615454;
font-weight: 700;
font-size: 0.8rem;
border-radius: 2px;
background: #62b574;
cursor: pointer;
padding: 0px 5px 0px 5px;
margin: 0px 5px 0px 5px;
line-height: 140%;
height: 1.1rem;
}
}
.beatrice-speaker-graph-container {
width: 20rem;
height: 19rem;
border: none;
}

View File

@ -1,11 +1,15 @@
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const webpack = require("webpack");
module.exports = {
mode: "production",
entry: "./src/000_index.tsx",
resolve: {
extensions: [".ts", ".tsx", ".js"],
fallback: {
buffer: require.resolve("buffer/"),
},
},
module: {
rules: [
@ -29,7 +33,7 @@ module.exports = {
test: /\.css$/,
use: ["style-loader", { loader: "css-loader", options: { importLoaders: 1 } }, "postcss-loader"],
},
{ test: /\.json$/, type: "asset/inline" },
],
},
output: {
@ -37,6 +41,9 @@ module.exports = {
path: path.resolve(__dirname, "dist"),
},
plugins: [
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html"),
filename: "./index.html",
@ -47,5 +54,45 @@ module.exports = {
new CopyPlugin({
patterns: [{ from: "public/favicon.ico", to: "favicon.ico" }],
}),
]
// new CopyPlugin({
// patterns: [{ from: "./node_modules/@dannadori/voice-changer-js/dist/ort-wasm-simd.wasm", to: "ort-wasm-simd.wasm" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "./node_modules/@dannadori/voice-changer-js/dist/tfjs-backend-wasm-simd.wasm", to: "tfjs-backend-wasm-simd.wasm" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "./node_modules/@dannadori/voice-changer-js/dist/process.js", to: "process.js" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_emb_pit_24000.bin", to: "models/rvcv2_emb_pit_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_amitaro_v2_32k_f0_24000.bin", to: "models/rvcv2_amitaro_v2_32k_f0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_amitaro_v2_32k_nof0_24000.bin", to: "models/rvcv2_amitaro_v2_32k_nof0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_amitaro_v2_40k_f0_24000.bin", to: "models/rvcv2_amitaro_v2_40k_f0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_amitaro_v2_40k_nof0_24000.bin", to: "models/rvcv2_amitaro_v2_40k_nof0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv1_emb_pit_24000.bin", to: "models/rvcv1_emb_pit_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv1_amitaro_v1_32k_f0_24000.bin", to: "models/rvcv1_amitaro_v1_32k_f0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv1_amitaro_v1_32k_nof0_24000.bin", to: "models/rvcv1_amitaro_v1_32k_nof0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv1_amitaro_v1_40k_f0_24000.bin", to: "models/rvcv1_amitaro_v1_40k_f0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv1_amitaro_v1_40k_nof0_24000.bin", to: "models/rvcv1_amitaro_v1_40k_nof0_24000.bin" }],
// }),
],
};

View File

@ -0,0 +1,111 @@
const path = require("path");
const HtmlWebpackPlugin = require("html-webpack-plugin");
const CopyPlugin = require("copy-webpack-plugin");
const webpack = require("webpack");
module.exports = {
mode: "production",
entry: "./src/000_index.tsx",
resolve: {
extensions: [".ts", ".tsx", ".js"],
fallback: {
buffer: require.resolve("buffer/"),
},
},
module: {
rules: [
{
test: [/\.ts$/, /\.tsx$/],
use: [
{
loader: "babel-loader",
options: {
presets: ["@babel/preset-env", "@babel/preset-react", "@babel/preset-typescript"],
plugins: ["@babel/plugin-transform-runtime"],
},
},
],
},
{
test: /\.html$/,
loader: "html-loader",
},
{
test: /\.css$/,
use: ["style-loader", { loader: "css-loader", options: { importLoaders: 1 } }, "postcss-loader"],
},
{ test: /\.json$/, type: "asset/inline" },
{ test: /\.svg$/, type: "asset/resource" },
],
},
output: {
filename: "index.js",
path: path.resolve(__dirname, "dist_web"),
},
plugins: [
new webpack.ProvidePlugin({
Buffer: ["buffer", "Buffer"],
}),
new HtmlWebpackPlugin({
template: path.resolve(__dirname, "public/index.html"),
filename: "./index.html",
}),
new CopyPlugin({
patterns: [{ from: "public/assets", to: "assets" }],
}),
new CopyPlugin({
patterns: [{ from: "public/favicon.ico", to: "favicon.ico" }],
}),
// ダミーファイルコピー
// new CopyPlugin({ //コピーの順番で上のassetのコピーで上書きされることがあるようだ。⇒npmスクリプトで対処。
// patterns: [{ from: "public/assets/gui_settings/edition_web.txt", to: "assets/gui_settings/edition.txt" }],
// }),
// new CopyPlugin({ // 拡張子なしのファイルコピーはできないようだ。⇒npmスクリプトで対処。
// patterns: [{ from: "public/info_web.txt", to: "info" }],
// }),
// VC用ファイルコピー
new CopyPlugin({
patterns: [{ from: "./node_modules/@dannadori/voice-changer-js/dist/ort-wasm-simd.wasm", to: "ort-wasm-simd.wasm" }],
}),
new CopyPlugin({
patterns: [{ from: "./node_modules/@dannadori/voice-changer-js/dist/tfjs-backend-wasm-simd.wasm", to: "tfjs-backend-wasm-simd.wasm" }],
}),
new CopyPlugin({
patterns: [{ from: "./node_modules/@dannadori/voice-changer-js/dist/process.js", to: "process.js" }],
}),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_emb_pit_24000.bin", to: "models/rvcv2_emb_pit_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_amitaro_v2_32k_f0_24000.bin", to: "models/rvcv2_amitaro_v2_32k_f0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_amitaro_v2_32k_nof0_24000.bin", to: "models/rvcv2_amitaro_v2_32k_nof0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_amitaro_v2_40k_f0_24000.bin", to: "models/rvcv2_amitaro_v2_40k_f0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv2_amitaro_v2_40k_nof0_24000.bin", to: "models/rvcv2_amitaro_v2_40k_nof0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv1_emb_pit_24000.bin", to: "models/rvcv1_emb_pit_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv1_amitaro_v1_32k_f0_24000.bin", to: "models/rvcv1_amitaro_v1_32k_f0_24000.bin" }],
// }),
// new CopyPlugin({
// patterns: [{ from: "public/models/rvcv1_amitaro_v1_32k_nof0_24000.bin", to: "models/rvcv1_amitaro_v1_32k_nof0_24000.bin" }],
// }),
new CopyPlugin({
patterns: [{ from: "public/models/rvcv2_exp_v2_32k_f0_24000.bin", to: "models/rvcv2_exp_v2_32k_f0_24000.bin" }],
}),
new CopyPlugin({
patterns: [{ from: "public/models/rvcv2_vctk_v2_16k_f0_24000.bin", to: "models/rvcv2_vctk_v2_16k_f0_24000.bin" }],
}),
// new CopyPlugin({
// patterns: [{ from: "public/models/amitaro.png", to: "models/amitaro.png" }],
// }),
],
};

View File

@ -0,0 +1,36 @@
const path = require("path");
const { merge } = require("webpack-merge");
const common = require("./webpack_web.common.js");
const express = require("express");
module.exports = merge(common, {
mode: "development",
devServer: {
setupMiddlewares: (middlewares, devServer) => {
if (!devServer) {
throw new Error("webpack-dev-server is not defined");
}
// ミドルウェアを追加して静的ファイルへのアクセスログを出力
devServer.app.use(
"/",
express.static(path.join(__dirname, "dist_web"), {
setHeaders: (res, filepath) => {
console.log(`Serving static file: ${filepath}`);
},
}),
);
// 既存のミドルウェアをそのまま利用
return middlewares;
},
client: {
overlay: {
errors: false,
warnings: false,
},
logging: "log",
},
host: "0.0.0.0",
https: true,
},
});

View File

@ -0,0 +1,6 @@
const { merge } = require("webpack-merge");
const common = require("./webpack_web.common.js");
module.exports = merge(common, {
mode: "production",
});

11
client/lib/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,11 @@
{
"workbench.colorCustomizations": {
"tab.activeBackground": "#65952acc"
},
"editor.defaultFormatter": "esbenp.prettier-vscode",
"prettier.printWidth": 1024,
"prettier.tabWidth": 4,
"files.associations": {
"*.css": "postcss"
}
}

File diff suppressed because it is too large Load Diff

View File

@ -1,57 +1,60 @@
{
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.159",
"description": "",
"main": "dist/index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"clean:worklet": "rimraf worklet/dist/",
"webpack:worklet:dev": "webpack --config webpack.worklet.dev.js",
"webpack:worklet:prod": "webpack --config webpack.worklet.prod.js",
"build:worklet:dev": "npm-run-all clean:worklet webpack:worklet:dev",
"build:worklet:prod": "npm-run-all clean:worklet webpack:worklet:prod",
"clean": "rimraf dist/",
"webpack:dev": "webpack --config webpack.dev.js",
"webpack:prod": "webpack --config webpack.prod.js",
"build:dev": "npm-run-all build:worklet:dev clean webpack:dev",
"build:prod": "npm-run-all build:worklet:prod clean webpack:prod",
"release": "npm version patch && npm publish --access=public",
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [
"voice conversion"
],
"author": "wataru.okada@flect.co.jp",
"license": "ISC",
"devDependencies": {
"@types/audioworklet": "^0.0.48",
"@types/node": "^20.3.3",
"@types/react": "18.2.14",
"@types/react-dom": "18.2.6",
"eslint": "^8.44.0",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-webpack-plugin": "^4.0.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.8.8",
"raw-loader": "^4.0.2",
"rimraf": "^5.0.1",
"ts-loader": "^9.4.4",
"typescript": "^5.1.6",
"webpack": "^5.88.1",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^4.15.1"
},
"dependencies": {
"@types/readable-stream": "^2.3.15",
"amazon-chime-sdk-js": "^3.15.0",
"buffer": "^6.0.3",
"localforage": "^1.10.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"socket.io-client": "^4.7.1"
}
"name": "@dannadori/voice-changer-client-js",
"version": "1.0.182",
"description": "",
"main": "dist/index.js",
"directories": {
"lib": "lib"
},
"scripts": {
"clean:worklet": "rimraf worklet/dist/",
"webpack:worklet:dev": "webpack --config webpack.worklet.dev.js",
"webpack:worklet:prod": "webpack --config webpack.worklet.prod.js",
"build:worklet:dev": "npm-run-all clean:worklet webpack:worklet:dev",
"build:worklet:prod": "npm-run-all clean:worklet webpack:worklet:prod",
"clean": "rimraf dist/",
"webpack:dev": "webpack --config webpack.dev.js",
"webpack:prod": "webpack --config webpack.prod.js",
"build:dev": "npm-run-all build:worklet:dev clean webpack:dev",
"build:prod": "npm-run-all build:worklet:prod clean webpack:prod",
"release": "npm version patch && npm publish --access=public",
"test": "jest"
},
"keywords": [
"voice conversion"
],
"author": "wataru.okada@flect.co.jp",
"license": "ISC",
"devDependencies": {
"@types/audioworklet": "^0.0.54",
"@types/jest": "^29.5.12",
"@types/node": "^20.11.21",
"@types/react": "18.2.60",
"@types/react-dom": "18.2.19",
"eslint": "^8.57.0",
"eslint-config-prettier": "^9.1.0",
"eslint-plugin-prettier": "^5.1.3",
"eslint-plugin-react": "^7.33.2",
"eslint-webpack-plugin": "^4.0.1",
"jest": "^29.7.0",
"npm-run-all": "^4.1.5",
"prettier": "^3.2.5",
"raw-loader": "^4.0.2",
"rimraf": "^5.0.5",
"ts-loader": "^9.5.1",
"typescript": "^5.3.3",
"webpack": "^5.90.3",
"webpack-cli": "^5.1.4",
"webpack-dev-server": "^5.0.2"
},
"dependencies": {
"@types/readable-stream": "^4.0.10",
"amazon-chime-sdk-js": "^3.20.0",
"buffer": "^6.0.3",
"localforage": "^1.10.0",
"protobufjs": "^7.2.6",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"socket.io-client": "^4.7.4"
}
}

View File

@ -5,14 +5,14 @@ export declare const RequestType: {
readonly stop: "stop";
readonly trancateBuffer: "trancateBuffer";
};
export type RequestType = typeof RequestType[keyof typeof RequestType];
export type RequestType = (typeof RequestType)[keyof typeof RequestType];
export declare const ResponseType: {
readonly volume: "volume";
readonly inputData: "inputData";
readonly start_ok: "start_ok";
readonly stop_ok: "stop_ok";
};
export type ResponseType = typeof ResponseType[keyof typeof ResponseType];
export type ResponseType = (typeof ResponseType)[keyof typeof ResponseType];
export type VoiceChangerWorkletProcessorRequest = {
requestType: RequestType;
voice: Float32Array;

View File

@ -1,84 +1,90 @@
import { VoiceChangerWorkletNode, VoiceChangerWorkletListener } from "./VoiceChangerWorkletNode";
import { VoiceChangerWorkletNode, VoiceChangerWorkletListener, InternalCallback } from "./client/VoiceChangerWorkletNode";
// @ts-ignore
import workerjs from "raw-loader!../worklet/dist/index.js";
import { VoiceFocusDeviceTransformer, VoiceFocusTransformDevice } from "amazon-chime-sdk-js";
import { createDummyMediaStream, validateUrl } from "./util";
import { DefaultClientSettng, MergeModelRequest, ServerSettingKey, VoiceChangerClientSetting, WorkletNodeSetting, WorkletSetting } from "./const";
import { ServerConfigurator } from "./ServerConfigurator";
import { ServerConfigurator } from "./client/ServerConfigurator";
// オーディオデータの流れ
// input node(mic or MediaStream) -> [vf node] -> [vc node] ->
// input node(mic or MediaStream) -> [vf node] -> [vc node] ->
// sio/rest server -> [vc node] -> output node
import { BlockingQueue } from "./utils/BlockingQueue";
export class VoiceChangerClient {
private configurator: ServerConfigurator
private ctx: AudioContext
private vfEnable = false
private vf: VoiceFocusDeviceTransformer | null = null
private currentDevice: VoiceFocusTransformDevice | null = null
private configurator: ServerConfigurator;
private ctx: AudioContext;
private vfEnable = false;
private vf: VoiceFocusDeviceTransformer | null = null;
private currentDevice: VoiceFocusTransformDevice | null = null;
private currentMediaStream: MediaStream | null = null
private currentMediaStreamAudioSourceNode: MediaStreamAudioSourceNode | null = null
private inputGainNode: GainNode | null = null
private outputGainNode: GainNode | null = null
private vcInNode!: VoiceChangerWorkletNode
private vcOutNode!: VoiceChangerWorkletNode
private currentMediaStreamAudioDestinationNode!: MediaStreamAudioDestinationNode
private currentMediaStream: MediaStream | null = null;
private currentMediaStreamAudioSourceNode: MediaStreamAudioSourceNode | null = null;
private inputGainNode: GainNode | null = null;
private outputGainNode: GainNode | null = null;
private monitorGainNode: GainNode | null = null;
private vcInNode!: VoiceChangerWorkletNode;
private vcOutNode!: VoiceChangerWorkletNode;
private currentMediaStreamAudioDestinationNode!: MediaStreamAudioDestinationNode;
private currentMediaStreamAudioDestinationMonitorNode!: MediaStreamAudioDestinationNode;
private promiseForInitialize: Promise<void>;
private _isVoiceChanging = false;
private promiseForInitialize: Promise<void>
private _isVoiceChanging = false
private setting: VoiceChangerClientSetting = DefaultClientSettng.voiceChangerClientSetting;
private setting: VoiceChangerClientSetting = DefaultClientSettng.voiceChangerClientSetting
private sslCertified: string[] = []
private sslCertified: string[] = [];
private sem = new BlockingQueue<number>();
constructor(ctx: AudioContext, vfEnable: boolean, voiceChangerWorkletListener: VoiceChangerWorkletListener) {
this.sem.enqueue(0);
this.configurator = new ServerConfigurator()
this.ctx = ctx
this.vfEnable = vfEnable
this.configurator = new ServerConfigurator("");
this.ctx = ctx;
this.vfEnable = vfEnable;
this.promiseForInitialize = new Promise<void>(async (resolve) => {
const scriptUrl = URL.createObjectURL(new Blob([workerjs], { type: "text/javascript" }));
// await this.ctx.audioWorklet.addModule(scriptUrl)
// this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
// this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
try {
this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
} catch (err) {
await this.ctx.audioWorklet.addModule(scriptUrl)
this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
await this.ctx.audioWorklet.addModule(scriptUrl);
this.vcInNode = new VoiceChangerWorkletNode(this.ctx, voiceChangerWorkletListener); // vc node
}
// const ctx44k = new AudioContext({ sampleRate: 44100 }) // これでもプチプチが残る
const ctx44k = new AudioContext({ sampleRate: 48000 }) // 結局これが一番まし。
console.log("audio out:", ctx44k)
const ctx44k = new AudioContext({ sampleRate: 48000 }); // 結局これが一番まし。
// const ctx44k = new AudioContext({ sampleRate: 16000 }); // LLVCテスト⇒16K出力でプチプチなしで行ける。
console.log("audio out:", ctx44k);
try {
this.vcOutNode = new VoiceChangerWorkletNode(ctx44k, voiceChangerWorkletListener); // vc node
this.vcOutNode = new VoiceChangerWorkletNode(ctx44k, voiceChangerWorkletListener); // vc node
} catch (err) {
await ctx44k.audioWorklet.addModule(scriptUrl)
this.vcOutNode = new VoiceChangerWorkletNode(ctx44k, voiceChangerWorkletListener); // vc node
await ctx44k.audioWorklet.addModule(scriptUrl);
this.vcOutNode = new VoiceChangerWorkletNode(ctx44k, voiceChangerWorkletListener); // vc node
}
this.currentMediaStreamAudioDestinationNode = ctx44k.createMediaStreamDestination() // output node
this.outputGainNode = ctx44k.createGain()
this.outputGainNode.gain.value = this.setting.outputGain
this.vcOutNode.connect(this.outputGainNode) // vc node -> output node
this.outputGainNode.connect(this.currentMediaStreamAudioDestinationNode)
this.currentMediaStreamAudioDestinationNode = ctx44k.createMediaStreamDestination(); // output node
this.outputGainNode = ctx44k.createGain();
this.outputGainNode.gain.value = this.setting.outputGain;
this.vcOutNode.connect(this.outputGainNode); // vc node -> output node
this.outputGainNode.connect(this.currentMediaStreamAudioDestinationNode);
this.currentMediaStreamAudioDestinationMonitorNode = ctx44k.createMediaStreamDestination(); // output node
this.monitorGainNode = ctx44k.createGain();
this.monitorGainNode.gain.value = this.setting.monitorGain;
this.vcOutNode.connect(this.monitorGainNode); // vc node -> monitor node
this.monitorGainNode.connect(this.currentMediaStreamAudioDestinationMonitorNode);
if (this.vfEnable) {
this.vf = await VoiceFocusDeviceTransformer.create({ variant: 'c20' })
const dummyMediaStream = createDummyMediaStream(this.ctx)
this.vf = await VoiceFocusDeviceTransformer.create({ variant: "c20" });
const dummyMediaStream = createDummyMediaStream(this.ctx);
this.currentDevice = (await this.vf.createTransformDevice(dummyMediaStream)) || null;
}
resolve()
})
resolve();
});
}
private lock = async () => {
@ -91,44 +97,46 @@ export class VoiceChangerClient {
isInitialized = async () => {
if (this.promiseForInitialize) {
await this.promiseForInitialize
await this.promiseForInitialize;
}
return true
}
return true;
};
/////////////////////////////////////////////////////
// オペレーション
/////////////////////////////////////////////////////
/// Operations ///
setup = async () => {
const lockNum = await this.lock()
const lockNum = await this.lock();
console.log(`Input Setup=> echo: ${this.setting.echoCancel}, noise1: ${this.setting.noiseSuppression}, noise2: ${this.setting.noiseSuppression2}`)
console.log(`Input Setup=> echo: ${this.setting.echoCancel}, noise1: ${this.setting.noiseSuppression}, noise2: ${this.setting.noiseSuppression2}`);
// condition check
if (!this.vcInNode) {
console.warn("vc node is not initialized.")
throw "vc node is not initialized."
console.warn("vc node is not initialized.");
throw "vc node is not initialized.";
}
// Main Process
//// shutdown & re-generate mediastream
if (this.currentMediaStream) {
this.currentMediaStream.getTracks().forEach(x => { x.stop() })
this.currentMediaStream = null
this.currentMediaStream.getTracks().forEach((x) => {
x.stop();
});
this.currentMediaStream = null;
}
//// Input デバイスがnullの時はmicStreamを止めてリターン
if (!this.setting.audioInput) {
console.log(`Input Setup=> client mic is disabled. ${this.setting.audioInput}`)
this.vcInNode.stop()
await this.unlock(lockNum)
return
console.log(`Input Setup=> client mic is disabled. ${this.setting.audioInput}`);
this.vcInNode.stop();
await this.unlock(lockNum);
return;
}
if (typeof this.setting.audioInput == "string") {
try {
if (this.setting.audioInput == "none") {
this.currentMediaStream = createDummyMediaStream(this.ctx)
this.currentMediaStream = createDummyMediaStream(this.ctx);
} else {
this.currentMediaStream = await navigator.mediaDevices.getUserMedia({
audio: {
@ -138,15 +146,15 @@ export class VoiceChangerClient {
sampleSize: 16,
autoGainControl: false,
echoCancellation: this.setting.echoCancel,
noiseSuppression: this.setting.noiseSuppression
}
})
noiseSuppression: this.setting.noiseSuppression,
},
});
}
} catch (e) {
console.warn(e)
this.vcInNode.stop()
await this.unlock(lockNum)
throw e
console.warn(e);
this.vcInNode.stop();
await this.unlock(lockNum);
throw e;
}
// this.currentMediaStream.getAudioTracks().forEach((x) => {
// console.log("MIC Setting(cap)", x.getCapabilities())
@ -154,19 +162,19 @@ export class VoiceChangerClient {
// console.log("MIC Setting(setting)", x.getSettings())
// })
} else {
this.currentMediaStream = this.setting.audioInput
this.currentMediaStream = this.setting.audioInput;
}
// connect nodes.
this.currentMediaStreamAudioSourceNode = this.ctx.createMediaStreamSource(this.currentMediaStream)
this.inputGainNode = this.ctx.createGain()
this.inputGainNode.gain.value = this.setting.inputGain
this.currentMediaStreamAudioSourceNode.connect(this.inputGainNode)
this.currentMediaStreamAudioSourceNode = this.ctx.createMediaStreamSource(this.currentMediaStream);
this.inputGainNode = this.ctx.createGain();
this.inputGainNode.gain.value = this.setting.inputGain;
this.currentMediaStreamAudioSourceNode.connect(this.inputGainNode);
if (this.currentDevice && this.setting.noiseSuppression2) {
this.currentDevice.chooseNewInnerDevice(this.currentMediaStream)
this.currentDevice.chooseNewInnerDevice(this.currentMediaStream);
const voiceFocusNode = await this.currentDevice.createAudioNode(this.ctx); // vf node
this.inputGainNode.connect(voiceFocusNode.start) // input node -> vf node
voiceFocusNode.end.connect(this.vcInNode)
this.inputGainNode.connect(voiceFocusNode.start); // input node -> vf node
voiceFocusNode.end.connect(this.vcInNode);
} else {
// console.log("input___ media stream", this.currentMediaStream)
// this.currentMediaStream.getTracks().forEach(x => {
@ -176,171 +184,184 @@ export class VoiceChangerClient {
// })
// console.log("input___ media node", this.currentMediaStreamAudioSourceNode)
// console.log("input___ gain node", this.inputGainNode.channelCount, this.inputGainNode)
this.inputGainNode.connect(this.vcInNode)
this.inputGainNode.connect(this.vcInNode);
}
this.vcInNode.setOutputNode(this.vcOutNode)
console.log("Input Setup=> success")
await this.unlock(lockNum)
}
this.vcInNode.setOutputNode(this.vcOutNode);
console.log("Input Setup=> success");
await this.unlock(lockNum);
};
get stream(): MediaStream {
return this.currentMediaStreamAudioDestinationNode.stream
return this.currentMediaStreamAudioDestinationNode.stream;
}
get monitorStream(): MediaStream {
return this.currentMediaStreamAudioDestinationMonitorNode.stream;
}
start = async () => {
await this.vcInNode.start()
this._isVoiceChanging = true
}
await this.vcInNode.start();
this._isVoiceChanging = true;
};
stop = async () => {
await this.vcInNode.stop()
this._isVoiceChanging = false
}
await this.vcInNode.stop();
this._isVoiceChanging = false;
};
get isVoiceChanging(): boolean {
return this._isVoiceChanging
return this._isVoiceChanging;
}
////////////////////////
/// 設定
//////////////////////////////
setServerUrl = (serverUrl: string, openTab: boolean = false) => {
const url = validateUrl(serverUrl)
const pageUrl = `${location.protocol}//${location.host}`
const url = validateUrl(serverUrl);
const pageUrl = `${location.protocol}//${location.host}`;
if (url != pageUrl && url.length != 0 && location.protocol == "https:" && this.sslCertified.includes(url) == false) {
if (openTab) {
const value = window.confirm("MMVC Server is different from this page's origin. Open tab to open ssl connection. OK? (You can close the opened tab after ssl connection succeed.)");
if (value) {
window.open(url, '_blank')
this.sslCertified.push(url)
window.open(url, "_blank");
this.sslCertified.push(url);
} else {
alert("Your voice conversion may fail...")
alert("Your voice conversion may fail...");
}
}
}
this.vcInNode.updateSetting({ ...this.vcInNode.getSettings(), serverUrl: url })
this.configurator.setServerUrl(url)
}
this.vcInNode.updateSetting({ ...this.vcInNode.getSettings(), serverUrl: url });
this.configurator = new ServerConfigurator(url);
};
updateClientSetting = async (setting: VoiceChangerClientSetting) => {
let reconstructInputRequired = false
if (
this.setting.audioInput != setting.audioInput ||
this.setting.echoCancel != setting.echoCancel ||
this.setting.noiseSuppression != setting.noiseSuppression ||
this.setting.noiseSuppression2 != setting.noiseSuppression2 ||
this.setting.sampleRate != setting.sampleRate
) {
reconstructInputRequired = true
let reconstructInputRequired = false;
if (this.setting.audioInput != setting.audioInput || this.setting.echoCancel != setting.echoCancel || this.setting.noiseSuppression != setting.noiseSuppression || this.setting.noiseSuppression2 != setting.noiseSuppression2 || this.setting.sampleRate != setting.sampleRate) {
reconstructInputRequired = true;
}
if (this.setting.inputGain != setting.inputGain) {
this.setInputGain(setting.inputGain)
this.setInputGain(setting.inputGain);
}
if (this.setting.outputGain != setting.outputGain) {
this.setOutputGain(setting.outputGain)
this.setOutputGain(setting.outputGain);
}
if (this.setting.monitorGain != setting.monitorGain) {
this.setMonitorGain(setting.monitorGain);
}
this.setting = setting
this.setting = setting;
if (reconstructInputRequired) {
await this.setup()
await this.setup();
}
}
};
setInputGain = (val: number) => {
this.setting.inputGain = val
this.setting.inputGain = val;
if (!this.inputGainNode) {
return
return;
}
this.inputGainNode.gain.value = val
}
if (!val) {
return;
}
this.inputGainNode.gain.value = val;
};
setOutputGain = (val: number) => {
if (!this.outputGainNode) {
return
return;
}
this.outputGainNode.gain.value = val
}
if (!val) {
return;
}
this.outputGainNode.gain.value = val;
};
setMonitorGain = (val: number) => {
if (!this.monitorGainNode) {
return;
}
if (!val) {
return;
}
this.monitorGainNode.gain.value = val;
};
/////////////////////////////////////////////////////
// コンポーネント設定、操作
/////////////////////////////////////////////////////
//## Server ##//
getModelType = () => {
return this.configurator.getModelType()
}
return this.configurator.getModelType();
};
getOnnx = async () => {
return this.configurator.export2onnx()
}
return this.configurator.export2onnx();
};
mergeModel = async (req: MergeModelRequest) => {
return this.configurator.mergeModel(req)
}
return this.configurator.mergeModel(req);
};
updateModelDefault = async () => {
return this.configurator.updateModelDefault()
}
return this.configurator.updateModelDefault();
};
updateModelInfo = async (slot: number, key: string, val: string) => {
return this.configurator.updateModelInfo(slot, key, val)
}
return this.configurator.updateModelInfo(slot, key, val);
};
updateServerSettings = (key: ServerSettingKey, val: string) => {
return this.configurator.updateSettings(key, val)
}
return this.configurator.updateSettings(key, val);
};
uploadFile = (buf: ArrayBuffer, filename: string, onprogress: (progress: number, end: boolean) => void) => {
return this.configurator.uploadFile(buf, filename, onprogress)
}
return this.configurator.uploadFile(buf, filename, onprogress);
};
uploadFile2 = (dir: string, file: File, onprogress: (progress: number, end: boolean) => void) => {
return this.configurator.uploadFile2(dir, file, onprogress)
}
return this.configurator.uploadFile2(dir, file, onprogress);
};
concatUploadedFile = (filename: string, chunkNum: number) => {
return this.configurator.concatUploadedFile(filename, chunkNum)
}
loadModel = (
slot: number,
isHalf: boolean,
params: string,
) => {
return this.configurator.loadModel(slot, isHalf, params)
}
return this.configurator.concatUploadedFile(filename, chunkNum);
};
loadModel = (slot: number, isHalf: boolean, params: string) => {
return this.configurator.loadModel(slot, isHalf, params);
};
uploadAssets = (params: string) => {
return this.configurator.uploadAssets(params)
}
return this.configurator.uploadAssets(params);
};
//## Worklet ##//
configureWorklet = (setting: WorkletSetting) => {
this.vcInNode.configure(setting)
this.vcOutNode.configure(setting)
}
this.vcInNode.configure(setting);
this.vcOutNode.configure(setting);
};
startOutputRecording = () => {
this.vcOutNode.startOutputRecording()
}
this.vcOutNode.startOutputRecording();
};
stopOutputRecording = () => {
return this.vcOutNode.stopOutputRecording()
}
return this.vcOutNode.stopOutputRecording();
};
trancateBuffer = () => {
this.vcOutNode.trancateBuffer()
}
this.vcOutNode.trancateBuffer();
};
//## Worklet Node ##//
updateWorkletNodeSetting = (setting: WorkletNodeSetting) => {
this.vcInNode.updateSetting(setting)
this.vcOutNode.updateSetting(setting)
}
this.vcInNode.updateSetting(setting);
this.vcOutNode.updateSetting(setting);
};
setInternalAudioProcessCallback = (internalCallback: InternalCallback) => {
this.vcInNode.setInternalAudioProcessCallback(internalCallback);
};
/////////////////////////////////////////////////////
// 情報取得
/////////////////////////////////////////////////////
// Information
getClientSettings = () => {
return this.vcInNode.getSettings()
}
return this.vcInNode.getSettings();
};
getServerSettings = () => {
return this.configurator.getSettings()
}
return this.configurator.getSettings();
};
getPerformance = () => {
return this.configurator.getPerformance()
}
return this.configurator.getPerformance();
};
getSocketId = () => {
return this.vcInNode.getSocketId()
}
}
return this.vcInNode.getSocketId();
};
}

View File

@ -1,402 +0,0 @@
import { VoiceChangerWorkletProcessorRequest } from "./@types/voice-changer-worklet-processor";
import { DefaultClientSettng, DownSamplingMode, VOICE_CHANGER_CLIENT_EXCEPTION, WorkletNodeSetting, WorkletSetting } from "./const";
import { io, Socket } from "socket.io-client";
import { DefaultEventsMap } from "@socket.io/component-emitter";
export type VoiceChangerWorkletListener = {
notifyVolume: (vol: number) => void
notifySendBufferingTime: (time: number) => void
notifyResponseTime: (time: number, perf?: number[]) => void
notifyException: (code: VOICE_CHANGER_CLIENT_EXCEPTION, message: string) => void
}
export class VoiceChangerWorkletNode extends AudioWorkletNode {
private listener: VoiceChangerWorkletListener
private setting: WorkletNodeSetting = DefaultClientSettng.workletNodeSetting
private requestChunks: ArrayBuffer[] = []
private socket: Socket<DefaultEventsMap, DefaultEventsMap> | null = null
// performance monitor
private bufferStart = 0;
private isOutputRecording = false;
private recordingOutputChunk: Float32Array[] = []
private outputNode: VoiceChangerWorkletNode | null = null
// Promises
private startPromiseResolve: ((value: void | PromiseLike<void>) => void) | null = null
private stopPromiseResolve: ((value: void | PromiseLike<void>) => void) | null = null
constructor(context: AudioContext, listener: VoiceChangerWorkletListener) {
super(context, "voice-changer-worklet-processor");
this.port.onmessage = this.handleMessage.bind(this);
this.listener = listener
this.createSocketIO()
console.log(`[worklet_node][voice-changer-worklet-processor] created.`);
}
setOutputNode = (outputNode: VoiceChangerWorkletNode | null) => {
this.outputNode = outputNode
}
// 設定
updateSetting = (setting: WorkletNodeSetting) => {
console.log(`[WorkletNode] Updating WorkletNode Setting,`, this.setting, setting)
let recreateSocketIoRequired = false
if (this.setting.serverUrl != setting.serverUrl || this.setting.protocol != setting.protocol) {
recreateSocketIoRequired = true
}
this.setting = setting
if (recreateSocketIoRequired) {
this.createSocketIO()
}
}
getSettings = (): WorkletNodeSetting => {
return this.setting
}
getSocketId = () => {
return this.socket?.id
}
// 処理
private createSocketIO = () => {
if (this.socket) {
this.socket.close()
}
if (this.setting.protocol === "sio") {
this.socket = io(this.setting.serverUrl + "/test");
this.socket.on('connect_error', (err) => {
this.listener.notifyException(VOICE_CHANGER_CLIENT_EXCEPTION.ERR_SIO_CONNECT_FAILED, `[SIO] rconnection failed ${err}`)
})
this.socket.on('connect', () => {
console.log(`[SIO] connect to ${this.setting.serverUrl}`)
console.log(`[SIO] ${this.socket?.id}`)
});
this.socket.on('close', function (socket) {
console.log(`[SIO] close ${socket.id}`)
});
this.socket.on('message', (response: any[]) => {
console.log("message:", response)
});
this.socket.on('response', (response: any[]) => {
const cur = Date.now()
const responseTime = cur - response[0]
const result = response[1] as ArrayBuffer
const perf = response[2]
// Quick hack for server device mode
if (response[0] == 0) {
this.listener.notifyResponseTime(Math.round(perf[0] * 1000), perf.slice(1, 4))
return
}
if (result.byteLength < 128 * 2) {
this.listener.notifyException(VOICE_CHANGER_CLIENT_EXCEPTION.ERR_SIO_INVALID_RESPONSE, `[SIO] recevied data is too short ${result.byteLength}`)
} else {
if (this.outputNode != null) {
this.outputNode.postReceivedVoice(response[1])
} else {
this.postReceivedVoice(response[1])
}
this.listener.notifyResponseTime(responseTime, perf)
}
});
}
}
postReceivedVoice = (data: ArrayBuffer) => {
// Int16 to Float
const i16Data = new Int16Array(data)
const f32Data = new Float32Array(i16Data.length)
// console.log(`[worklet] f32DataLength${f32Data.length} i16DataLength${i16Data.length}`)
i16Data.forEach((x, i) => {
const float = (x >= 0x8000) ? -(0x10000 - x) / 0x8000 : x / 0x7FFF;
f32Data[i] = float
})
// アップサンプリング
let upSampledBuffer: Float32Array | null = null
if (this.setting.sendingSampleRate == 48000) {
upSampledBuffer = f32Data
} else {
upSampledBuffer = new Float32Array(f32Data.length * 2)
for (let i = 0; i < f32Data.length; i++) {
const currentFrame = f32Data[i]
const nextFrame = i + 1 < f32Data.length ? f32Data[i + 1] : f32Data[i]
upSampledBuffer[i * 2] = currentFrame
upSampledBuffer[i * 2 + 1] = (currentFrame + nextFrame) / 2
}
}
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "voice",
voice: upSampledBuffer,
numTrancateTreshold: 0,
volTrancateThreshold: 0,
volTrancateLength: 0
}
this.port.postMessage(req)
if (this.isOutputRecording) {
this.recordingOutputChunk.push(upSampledBuffer)
}
}
private _averageDownsampleBuffer(buffer: Float32Array, originalSampleRate: number, destinationSamplerate: number) {
if (originalSampleRate == destinationSamplerate) {
return buffer;
}
if (destinationSamplerate > originalSampleRate) {
throw "downsampling rate show be smaller than original sample rate";
}
const sampleRateRatio = originalSampleRate / destinationSamplerate;
const newLength = Math.round(buffer.length / sampleRateRatio);
const result = new Float32Array(newLength);
let offsetResult = 0;
let offsetBuffer = 0;
while (offsetResult < result.length) {
var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
// Use average value of skipped samples
var accum = 0, count = 0;
for (var i = offsetBuffer; i < nextOffsetBuffer && i < buffer.length; i++) {
accum += buffer[i];
count++;
}
result[offsetResult] = accum / count;
// Or you can simply get rid of the skipped samples:
// result[offsetResult] = buffer[nextOffsetBuffer];
offsetResult++;
offsetBuffer = nextOffsetBuffer;
}
return result;
}
handleMessage(event: any) {
// console.log(`[Node:handleMessage_] `, event.data.volume);
if (event.data.responseType === "start_ok") {
if (this.startPromiseResolve) {
this.startPromiseResolve()
this.startPromiseResolve = null
}
} else if (event.data.responseType === "stop_ok") {
if (this.stopPromiseResolve) {
this.stopPromiseResolve()
this.stopPromiseResolve = null
}
} else if (event.data.responseType === "volume") {
this.listener.notifyVolume(event.data.volume as number)
} else if (event.data.responseType === "inputData") {
const inputData = event.data.inputData as Float32Array
// console.log("receive input data", inputData)
// ダウンサンプリング
let downsampledBuffer: Float32Array | null = null
if (this.setting.sendingSampleRate == 48000) {
downsampledBuffer = inputData
} else if (this.setting.downSamplingMode == DownSamplingMode.decimate) {
//////// (Kind 1) 間引き //////////
//// 48000Hz で入ってくるので間引いて24000Hzに変換する。
downsampledBuffer = new Float32Array(inputData.length / 2);
for (let i = 0; i < inputData.length; i++) {
if (i % 2 == 0) {
downsampledBuffer[i / 2] = inputData[i]
}
}
} else {
//////// (Kind 2) 平均 //////////
// downsampledBuffer = this._averageDownsampleBuffer(buffer, 48000, 24000)
downsampledBuffer = this._averageDownsampleBuffer(inputData, 48000, this.setting.sendingSampleRate)
}
// Float to Int16
const arrayBuffer = new ArrayBuffer(downsampledBuffer.length * 2)
const dataView = new DataView(arrayBuffer);
for (let i = 0; i < downsampledBuffer.length; i++) {
let s = Math.max(-1, Math.min(1, downsampledBuffer[i]));
s = s < 0 ? s * 0x8000 : s * 0x7FFF
dataView.setInt16(i * 2, s, true);
}
// バッファリング
this.requestChunks.push(arrayBuffer)
//// リクエストバッファの中身が、リクエスト送信数と違う場合は処理終了。
if (this.requestChunks.length < this.setting.inputChunkNum) {
return
}
// リクエスト用の入れ物を作成
const windowByteLength = this.requestChunks.reduce((prev, cur) => {
return prev + cur.byteLength
}, 0)
const newBuffer = new Uint8Array(windowByteLength);
// リクエストのデータをセット
this.requestChunks.reduce((prev, cur) => {
newBuffer.set(new Uint8Array(cur), prev)
return prev + cur.byteLength
}, 0)
this.sendBuffer(newBuffer)
this.requestChunks = []
this.listener.notifySendBufferingTime(Date.now() - this.bufferStart)
this.bufferStart = Date.now()
} else {
console.warn(`[worklet_node][voice-changer-worklet-processor] unknown response ${event.data.responseType}`, event.data)
}
}
private sendBuffer = async (newBuffer: Uint8Array) => {
const timestamp = Date.now()
if (this.setting.protocol === "sio") {
if (!this.socket) {
console.warn(`sio is not initialized`)
return
}
// console.log("emit!")
this.socket.emit('request_message', [
timestamp,
newBuffer.buffer]);
} else {
const res = await postVoice(
this.setting.serverUrl + "/test",
timestamp,
newBuffer.buffer)
if (res.byteLength < 128 * 2) {
this.listener.notifyException(VOICE_CHANGER_CLIENT_EXCEPTION.ERR_REST_INVALID_RESPONSE, `[REST] recevied data is too short ${res.byteLength}`)
} else {
if (this.outputNode != null) {
this.outputNode.postReceivedVoice(res)
} else {
this.postReceivedVoice(res)
}
this.listener.notifyResponseTime(Date.now() - timestamp)
}
}
}
configure = (setting: WorkletSetting) => {
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "config",
voice: new Float32Array(1),
numTrancateTreshold: setting.numTrancateTreshold,
volTrancateThreshold: setting.volTrancateThreshold,
volTrancateLength: setting.volTrancateLength
}
this.port.postMessage(req)
}
start = async () => {
const p = new Promise<void>((resolve) => {
this.startPromiseResolve = resolve
})
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "start",
voice: new Float32Array(1),
numTrancateTreshold: 0,
volTrancateThreshold: 0,
volTrancateLength: 0
}
this.port.postMessage(req)
await p
}
stop = async () => {
const p = new Promise<void>((resolve) => {
this.stopPromiseResolve = resolve
})
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "stop",
voice: new Float32Array(1),
numTrancateTreshold: 0,
volTrancateThreshold: 0,
volTrancateLength: 0
}
this.port.postMessage(req)
await p
}
trancateBuffer = () => {
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "trancateBuffer",
voice: new Float32Array(1),
numTrancateTreshold: 0,
volTrancateThreshold: 0,
volTrancateLength: 0
}
this.port.postMessage(req)
}
startOutputRecording = () => {
this.recordingOutputChunk = []
this.isOutputRecording = true
}
stopOutputRecording = () => {
this.isOutputRecording = false
const dataSize = this.recordingOutputChunk.reduce((prev, cur) => {
return prev + cur.length
}, 0)
const samples = new Float32Array(dataSize);
let sampleIndex = 0
for (let i = 0; i < this.recordingOutputChunk.length; i++) {
for (let j = 0; j < this.recordingOutputChunk[i].length; j++) {
samples[sampleIndex] = this.recordingOutputChunk[i][j];
sampleIndex++;
}
}
return samples
}
}
export const postVoice = async (
url: string,
timestamp: number,
buffer: ArrayBuffer) => {
const obj = {
timestamp,
buffer: Buffer.from(buffer).toString('base64')
};
const body = JSON.stringify(obj);
const res = await fetch(`${url}`, {
method: "POST",
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: body
})
try {
const receivedJson = await res.json()
const changedVoiceBase64 = receivedJson["changedVoiceBase64"]
const buf = Buffer.from(changedVoiceBase64, "base64")
const ab = new ArrayBuffer(buf.length);
const view = new Uint8Array(ab);
for (let i = 0; i < buf.length; ++i) {
view[i] = buf[i];
}
return ab
} catch (e) {
console.log("Exception:", e)
return new ArrayBuffer(10);
}
}

View File

@ -0,0 +1,62 @@
import { MergeModelRequest, ServerSettingKey } from "../const";
import { ServerRestClient } from "./ServerRestClient";
export class ServerConfigurator {
private restClient;
constructor(serverUrl: string) {
this.restClient = new ServerRestClient(serverUrl);
}
getSettings = async () => {
return this.restClient.getSettings();
};
getPerformance = async () => {
return this.restClient.getPerformance();
};
updateSettings = async (key: ServerSettingKey, val: string) => {
return this.restClient.updateSettings(key, val);
};
uploadFile2 = async (dir: string, file: File, onprogress: (progress: number, end: boolean) => void) => {
return this.restClient.uploadFile2(dir, file, onprogress);
};
uploadFile = async (buf: ArrayBuffer, filename: string, onprogress: (progress: number, end: boolean) => void) => {
return this.restClient.uploadFile(buf, filename, onprogress);
};
concatUploadedFile = async (filename: string, chunkNum: number) => {
return this.restClient.concatUploadedFile(filename, chunkNum);
};
loadModel = async (slot: number, isHalf: boolean, params: string = "{}") => {
return this.restClient.loadModel(slot, isHalf, params);
};
uploadAssets = async (params: string) => {
return this.restClient.uploadAssets(params);
};
getModelType = async () => {
return this.restClient.getModelType();
};
export2onnx = async () => {
return this.restClient.export2onnx();
};
mergeModel = async (req: MergeModelRequest) => {
return this.restClient.mergeModel(req);
};
updateModelDefault = async () => {
return this.restClient.updateModelDefault();
};
updateModelInfo = async (slot: number, key: string, val: string) => {
return this.restClient.updateModelInfo(slot, key, val);
};
}

View File

@ -1,108 +1,107 @@
import { MergeModelRequest, OnnxExporterInfo, ServerInfo, ServerSettingKey } from "./const";
import { MergeModelRequest, OnnxExporterInfo, ServerInfo, ServerSettingKey } from "../const";
type FileChunk = {
hash: number,
chunk: ArrayBuffer
}
export class ServerConfigurator {
private serverUrl = ""
hash: number;
chunk: ArrayBuffer;
};
setServerUrl = (serverUrl: string) => {
this.serverUrl = serverUrl
console.log(`[ServerConfigurator] Server URL: ${this.serverUrl}`)
export class ServerRestClient {
private serverUrl = "";
constructor(serverUrl: string) {
this.serverUrl = serverUrl;
}
getSettings = async () => {
const url = this.serverUrl + "/info"
const url = this.serverUrl + "/info";
const info = await new Promise<ServerInfo>((resolve) => {
const request = new Request(url, {
method: 'GET',
method: "GET",
});
fetch(request).then(async (response) => {
const json = await response.json() as ServerInfo
resolve(json)
})
})
return info
}
const json = (await response.json()) as ServerInfo;
resolve(json);
});
});
return info;
};
getPerformance = async () => {
const url = this.serverUrl + "/performance"
const url = this.serverUrl + "/performance";
const info = await new Promise<number[]>((resolve) => {
const request = new Request(url, {
method: 'GET',
method: "GET",
});
fetch(request).then(async (response) => {
const json = await response.json() as number[]
resolve(json)
})
})
return info
}
const json = (await response.json()) as number[];
resolve(json);
});
});
return info;
};
updateSettings = async (key: ServerSettingKey, val: string) => {
const url = this.serverUrl + "/update_settings"
const url = this.serverUrl + "/update_settings";
const info = await new Promise<ServerInfo>(async (resolve) => {
const formData = new FormData();
formData.append("key", key);
formData.append("val", val);
const request = new Request(url, {
method: 'POST',
method: "POST",
body: formData,
});
const res = await (await fetch(request)).json() as ServerInfo
resolve(res)
})
return info
}
const res = (await (await fetch(request)).json()) as ServerInfo;
resolve(res);
});
return info;
};
uploadFile2 = async (dir: string, file: File, onprogress: (progress: number, end: boolean) => void) => {
const url = this.serverUrl + "/upload_file"
onprogress(0, false)
const url = this.serverUrl + "/upload_file";
onprogress(0, false);
const size = 1024 * 1024;
let index = 0; // index値
const fileLength = file.size
const filename = dir + file.name
const fileChunkNum = Math.ceil(fileLength / size)
const fileLength = file.size;
const filename = dir + file.name;
const fileChunkNum = Math.ceil(fileLength / size);
while (true) {
const promises: Promise<void>[] = []
const promises: Promise<void>[] = [];
for (let i = 0; i < 10; i++) {
if (index * size >= fileLength) {
break
break;
}
const chunk = file.slice(index * size, (index + 1) * size)
const chunk = file.slice(index * size, (index + 1) * size);
const p = new Promise<void>((resolve) => {
const formData = new FormData();
formData.append("file", new Blob([chunk]));
formData.append("filename", `${filename}_${index}`);
const request = new Request(url, {
method: 'POST',
method: "POST",
body: formData,
});
fetch(request).then(async (_response) => {
// console.log(await response.text())
resolve()
})
})
index += 1
promises.push(p)
resolve();
});
});
index += 1;
promises.push(p);
}
await Promise.all(promises)
await Promise.all(promises);
if (index * size >= fileLength) {
break
break;
}
onprogress(Math.floor(((index) / (fileChunkNum + 1)) * 100), false)
onprogress(Math.floor((index / (fileChunkNum + 1)) * 100), false);
}
return fileChunkNum
}
return fileChunkNum;
};
uploadFile = async (buf: ArrayBuffer, filename: string, onprogress: (progress: number, end: boolean) => void) => {
const url = this.serverUrl + "/upload_file"
onprogress(0, false)
const url = this.serverUrl + "/upload_file";
onprogress(0, false);
const size = 1024 * 1024;
const fileChunks: FileChunk[] = [];
let index = 0; // index値
@ -113,65 +112,64 @@ export class ServerConfigurator {
});
}
const chunkNum = fileChunks.length
const chunkNum = fileChunks.length;
// console.log("FILE_CHUNKS:", chunkNum, fileChunks)
while (true) {
const promises: Promise<void>[] = []
const promises: Promise<void>[] = [];
for (let i = 0; i < 10; i++) {
const chunk = fileChunks.shift()
const chunk = fileChunks.shift();
if (!chunk) {
break
break;
}
const p = new Promise<void>((resolve) => {
const formData = new FormData();
formData.append("file", new Blob([chunk.chunk]));
formData.append("filename", `${filename}_${chunk.hash}`);
const request = new Request(url, {
method: 'POST',
method: "POST",
body: formData,
});
fetch(request).then(async (_response) => {
// console.log(await response.text())
resolve()
})
})
resolve();
});
});
promises.push(p)
promises.push(p);
}
await Promise.all(promises)
await Promise.all(promises);
if (fileChunks.length == 0) {
break
break;
}
onprogress(Math.floor(((chunkNum - fileChunks.length) / (chunkNum + 1)) * 100), false)
onprogress(Math.floor(((chunkNum - fileChunks.length) / (chunkNum + 1)) * 100), false);
}
return chunkNum
}
return chunkNum;
};
concatUploadedFile = async (filename: string, chunkNum: number) => {
const url = this.serverUrl + "/concat_uploaded_file"
const url = this.serverUrl + "/concat_uploaded_file";
await new Promise<void>((resolve) => {
const formData = new FormData();
formData.append("filename", filename);
formData.append("filenameChunkNum", "" + chunkNum);
const request = new Request(url, {
method: 'POST',
method: "POST",
body: formData,
});
fetch(request).then(async (response) => {
console.log(await response.text())
resolve()
})
})
}
console.log(await response.text());
resolve();
});
});
};
loadModel = async (slot: number, isHalf: boolean, params: string = "{}") => {
if (isHalf == undefined || isHalf == null) {
console.warn("isHalf is invalid value", isHalf)
isHalf = false
console.warn("isHalf is invalid value", isHalf);
isHalf = false;
}
const url = this.serverUrl + "/load_model"
const url = this.serverUrl + "/load_model";
const info = new Promise<ServerInfo>(async (resolve) => {
const formData = new FormData();
formData.append("slot", "" + slot);
@ -179,102 +177,137 @@ export class ServerConfigurator {
formData.append("params", params);
const request = new Request(url, {
method: 'POST',
method: "POST",
body: formData,
});
const res = await (await fetch(request)).json() as ServerInfo
resolve(res)
})
return await info
}
const res = (await (await fetch(request)).json()) as ServerInfo;
resolve(res);
});
return await info;
};
uploadAssets = async (params: string) => {
const url = this.serverUrl + "/upload_model_assets"
const url = this.serverUrl + "/upload_model_assets";
const info = new Promise<ServerInfo>(async (resolve) => {
const formData = new FormData();
formData.append("params", params);
const request = new Request(url, {
method: 'POST',
method: "POST",
body: formData,
});
const res = await (await fetch(request)).json() as ServerInfo
resolve(res)
})
return await info
}
const res = (await (await fetch(request)).json()) as ServerInfo;
resolve(res);
});
return await info;
};
getModelType = async () => {
const url = this.serverUrl + "/model_type"
const url = this.serverUrl + "/model_type";
const info = new Promise<ServerInfo>(async (resolve) => {
const request = new Request(url, {
method: 'GET',
method: "GET",
});
const res = await (await fetch(request)).json() as ServerInfo
resolve(res)
})
return await info
}
const res = (await (await fetch(request)).json()) as ServerInfo;
resolve(res);
});
return await info;
};
export2onnx = async () => {
const url = this.serverUrl + "/onnx"
const url = this.serverUrl + "/onnx";
const info = new Promise<OnnxExporterInfo>(async (resolve) => {
const request = new Request(url, {
method: 'GET',
method: "GET",
});
const res = await (await fetch(request)).json() as OnnxExporterInfo
resolve(res)
})
return await info
}
const res = (await (await fetch(request)).json()) as OnnxExporterInfo;
resolve(res);
});
return await info;
};
mergeModel = async (req: MergeModelRequest) => {
const url = this.serverUrl + "/merge_model"
const url = this.serverUrl + "/merge_model";
const info = new Promise<ServerInfo>(async (resolve) => {
const formData = new FormData();
formData.append("request", JSON.stringify(req));
const request = new Request(url, {
method: 'POST',
method: "POST",
body: formData,
});
const res = await (await fetch(request)).json() as ServerInfo
console.log("RESPONSE", res)
resolve(res)
})
return await info
}
const res = (await (await fetch(request)).json()) as ServerInfo;
console.log("RESPONSE", res);
resolve(res);
});
return await info;
};
updateModelDefault = async () => {
const url = this.serverUrl + "/update_model_default"
const url = this.serverUrl + "/update_model_default";
const info = new Promise<ServerInfo>(async (resolve) => {
const request = new Request(url, {
method: 'POST',
method: "POST",
});
const res = await (await fetch(request)).json() as ServerInfo
console.log("RESPONSE", res)
resolve(res)
})
return await info
}
const res = (await (await fetch(request)).json()) as ServerInfo;
console.log("RESPONSE", res);
resolve(res);
});
return await info;
};
updateModelInfo = async (slot: number, key: string, val: string) => {
const url = this.serverUrl + "/update_model_info"
const newData = { slot, key, val }
const url = this.serverUrl + "/update_model_info";
const newData = { slot, key, val };
const info = new Promise<ServerInfo>(async (resolve) => {
const formData = new FormData();
formData.append("newData", JSON.stringify(newData));
const request = new Request(url, {
method: 'POST',
method: "POST",
body: formData,
});
const res = await (await fetch(request)).json() as ServerInfo
console.log("RESPONSE", res)
resolve(res)
})
return await info
}
const res = (await (await fetch(request)).json()) as ServerInfo;
console.log("RESPONSE", res);
resolve(res);
});
return await info;
};
// VoiceChangerWorkletNodeから呼び出される
//// Restで音声変換
postVoice = async (timestamp: number, buffer: ArrayBuffer) => {
const url = this.serverUrl + "/test";
const obj = {
timestamp,
buffer: Buffer.from(buffer).toString("base64"),
};
const body = JSON.stringify(obj);
const res = await fetch(`${url}`, {
method: "POST",
headers: {
Accept: "application/json",
"Content-Type": "application/json",
},
body: body,
});
try {
const receivedJson = await res.json();
const changedVoiceBase64 = receivedJson["changedVoiceBase64"];
const buf = Buffer.from(changedVoiceBase64, "base64");
const ab = new ArrayBuffer(buf.length);
const view = new Uint8Array(ab);
for (let i = 0; i < buf.length; ++i) {
view[i] = buf[i];
}
return ab;
} catch (e) {
console.log("Exception:", e);
return new ArrayBuffer(10);
}
};
}

View File

@ -0,0 +1,9 @@
describe("test1", () => {
test("test222", () => {
expect(
(() => {
return 1;
})()
).toBe(1);
});
});

View File

@ -0,0 +1,443 @@
import { VoiceChangerWorkletProcessorRequest } from "../@types/voice-changer-worklet-processor";
import {
DefaultClientSettng,
DownSamplingMode,
VOICE_CHANGER_CLIENT_EXCEPTION,
WorkletNodeSetting,
WorkletSetting,
} from "../const";
import { io, Socket } from "socket.io-client";
import { DefaultEventsMap } from "@socket.io/component-emitter";
import { ServerRestClient } from "./ServerRestClient";
export type VoiceChangerWorkletListener = {
notifyVolume: (vol: number) => void;
notifySendBufferingTime: (time: number) => void;
notifyResponseTime: (time: number, perf?: number[]) => void;
notifyException: (
code: VOICE_CHANGER_CLIENT_EXCEPTION,
message: string
) => void;
};
export type InternalCallback = {
processAudio: (data: Uint8Array) => Promise<Uint8Array>;
};
export class VoiceChangerWorkletNode extends AudioWorkletNode {
private listener: VoiceChangerWorkletListener;
private setting: WorkletNodeSetting = DefaultClientSettng.workletNodeSetting;
private requestChunks: ArrayBuffer[] = [];
private socket: Socket<DefaultEventsMap, DefaultEventsMap> | null = null;
// performance monitor
private bufferStart = 0;
private isOutputRecording = false;
private recordingOutputChunk: Float32Array[] = [];
private outputNode: VoiceChangerWorkletNode | null = null;
// Promises
private startPromiseResolve:
| ((value: void | PromiseLike<void>) => void)
| null = null;
private stopPromiseResolve:
| ((value: void | PromiseLike<void>) => void)
| null = null;
// InternalCallback
private internalCallback: InternalCallback | null = null;
constructor(context: AudioContext, listener: VoiceChangerWorkletListener) {
super(context, "voice-changer-worklet-processor");
this.port.onmessage = this.handleMessage.bind(this);
this.listener = listener;
this.createSocketIO();
console.log(`[worklet_node][voice-changer-worklet-processor] created.`);
}
setOutputNode = (outputNode: VoiceChangerWorkletNode | null) => {
this.outputNode = outputNode;
};
// 設定
updateSetting = (setting: WorkletNodeSetting) => {
console.log(
`[WorkletNode] Updating WorkletNode Setting,`,
this.setting,
setting
);
let recreateSocketIoRequired = false;
if (
this.setting.serverUrl != setting.serverUrl ||
this.setting.protocol != setting.protocol
) {
recreateSocketIoRequired = true;
}
this.setting = setting;
if (recreateSocketIoRequired) {
this.createSocketIO();
}
};
setInternalAudioProcessCallback = (internalCallback: InternalCallback) => {
this.internalCallback = internalCallback;
};
getSettings = (): WorkletNodeSetting => {
return this.setting;
};
getSocketId = () => {
return this.socket?.id;
};
// 処理
private createSocketIO = () => {
if (this.socket) {
this.socket.close();
}
if (this.setting.protocol === "sio") {
this.socket = io(this.setting.serverUrl + "/test");
this.socket.on("connect_error", (err) => {
this.listener.notifyException(
VOICE_CHANGER_CLIENT_EXCEPTION.ERR_SIO_CONNECT_FAILED,
`[SIO] rconnection failed ${err}`
);
});
this.socket.on("connect", () => {
console.log(`[SIO] connect to ${this.setting.serverUrl}`);
console.log(`[SIO] ${this.socket?.id}`);
});
this.socket.on("close", function (socket) {
console.log(`[SIO] close ${socket.id}`);
});
this.socket.on("message", (response: any[]) => {
console.log("message:", response);
});
this.socket.on("response", (response: any[]) => {
const cur = Date.now();
const responseTime = cur - response[0];
const result = response[1] as ArrayBuffer;
const perf = response[2];
// Quick hack for server device mode
if (response[0] == 0) {
this.listener.notifyResponseTime(
Math.round(perf[0] * 1000),
perf.slice(1, 4)
);
return;
}
if (result.byteLength < 128 * 2) {
this.listener.notifyException(
VOICE_CHANGER_CLIENT_EXCEPTION.ERR_SIO_INVALID_RESPONSE,
`[SIO] recevied data is too short ${result.byteLength}`
);
} else {
if (this.outputNode != null) {
this.outputNode.postReceivedVoice(response[1]);
} else {
this.postReceivedVoice(response[1]);
}
this.listener.notifyResponseTime(responseTime, perf);
}
});
}
};
postReceivedVoice = (data: ArrayBuffer) => {
// Int16 to Float
const i16Data = new Int16Array(data);
const f32Data = new Float32Array(i16Data.length);
// console.log(`[worklet] f32DataLength${f32Data.length} i16DataLength${i16Data.length}`)
i16Data.forEach((x, i) => {
const float = x >= 0x8000 ? -(0x10000 - x) / 0x8000 : x / 0x7fff;
f32Data[i] = float;
});
// アップサンプリング
let upSampledBuffer: Float32Array | null = null;
if (this.setting.sendingSampleRate == 48000) {
upSampledBuffer = f32Data;
} else {
upSampledBuffer = new Float32Array(f32Data.length * 2);
for (let i = 0; i < f32Data.length; i++) {
const currentFrame = f32Data[i];
const nextFrame = i + 1 < f32Data.length ? f32Data[i + 1] : f32Data[i];
upSampledBuffer[i * 2] = currentFrame;
upSampledBuffer[i * 2 + 1] = (currentFrame + nextFrame) / 2;
}
}
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "voice",
voice: upSampledBuffer,
numTrancateTreshold: 0,
volTrancateThreshold: 0,
volTrancateLength: 0,
};
this.port.postMessage(req);
if (this.isOutputRecording) {
this.recordingOutputChunk.push(upSampledBuffer);
}
};
private _averageDownsampleBuffer(
buffer: Float32Array,
originalSampleRate: number,
destinationSamplerate: number
) {
if (originalSampleRate == destinationSamplerate) {
return buffer;
}
if (destinationSamplerate > originalSampleRate) {
throw "downsampling rate show be smaller than original sample rate";
}
const sampleRateRatio = originalSampleRate / destinationSamplerate;
const newLength = Math.round(buffer.length / sampleRateRatio);
const result = new Float32Array(newLength);
let offsetResult = 0;
let offsetBuffer = 0;
while (offsetResult < result.length) {
var nextOffsetBuffer = Math.round((offsetResult + 1) * sampleRateRatio);
// Use average value of skipped samples
var accum = 0,
count = 0;
for (
var i = offsetBuffer;
i < nextOffsetBuffer && i < buffer.length;
i++
) {
accum += buffer[i];
count++;
}
result[offsetResult] = accum / count;
// Or you can simply get rid of the skipped samples:
// result[offsetResult] = buffer[nextOffsetBuffer];
offsetResult++;
offsetBuffer = nextOffsetBuffer;
}
return result;
}
handleMessage(event: any) {
// console.log(`[Node:handleMessage_] `, event.data.volume);
if (event.data.responseType === "start_ok") {
if (this.startPromiseResolve) {
this.startPromiseResolve();
this.startPromiseResolve = null;
}
} else if (event.data.responseType === "stop_ok") {
if (this.stopPromiseResolve) {
this.stopPromiseResolve();
this.stopPromiseResolve = null;
}
} else if (event.data.responseType === "volume") {
this.listener.notifyVolume(event.data.volume as number);
} else if (event.data.responseType === "inputData") {
const inputData = event.data.inputData as Float32Array;
// console.log("receive input data", inputData);
// ダウンサンプリング
let downsampledBuffer: Float32Array | null = null;
if (this.setting.sendingSampleRate == 48000) {
downsampledBuffer = inputData;
} else if (this.setting.downSamplingMode == DownSamplingMode.decimate) {
//////// (Kind 1) 間引き //////////
//// 48000Hz で入ってくるので間引いて24000Hzに変換する。
downsampledBuffer = new Float32Array(inputData.length / 2);
for (let i = 0; i < inputData.length; i++) {
if (i % 2 == 0) {
downsampledBuffer[i / 2] = inputData[i];
}
}
} else {
//////// (Kind 2) 平均 //////////
// downsampledBuffer = this._averageDownsampleBuffer(buffer, 48000, 24000)
downsampledBuffer = this._averageDownsampleBuffer(
inputData,
48000,
this.setting.sendingSampleRate
);
}
// Float to Int16 (internalの場合はfloatのまま行く。)
if (this.setting.protocol != "internal") {
const arrayBuffer = new ArrayBuffer(downsampledBuffer.length * 2);
const dataView = new DataView(arrayBuffer);
for (let i = 0; i < downsampledBuffer.length; i++) {
let s = Math.max(-1, Math.min(1, downsampledBuffer[i]));
s = s < 0 ? s * 0x8000 : s * 0x7fff;
dataView.setInt16(i * 2, s, true);
}
// バッファリング
this.requestChunks.push(arrayBuffer);
} else {
// internal
// console.log("downsampledBuffer.buffer", downsampledBuffer.buffer);
this.requestChunks.push(downsampledBuffer.buffer);
}
//// リクエストバッファの中身が、リクエスト送信数と違う場合は処理終了。
if (this.requestChunks.length < this.setting.inputChunkNum) {
return;
}
// リクエスト用の入れ物を作成
const windowByteLength = this.requestChunks.reduce((prev, cur) => {
return prev + cur.byteLength;
}, 0);
const newBuffer = new Uint8Array(windowByteLength);
// リクエストのデータをセット
this.requestChunks.reduce((prev, cur) => {
newBuffer.set(new Uint8Array(cur), prev);
return prev + cur.byteLength;
}, 0);
this.sendBuffer(newBuffer);
this.requestChunks = [];
this.listener.notifySendBufferingTime(Date.now() - this.bufferStart);
this.bufferStart = Date.now();
} else {
console.warn(
`[worklet_node][voice-changer-worklet-processor] unknown response ${event.data.responseType}`,
event.data
);
}
}
private sendBuffer = async (newBuffer: Uint8Array) => {
const timestamp = Date.now();
if (this.setting.protocol === "sio") {
if (!this.socket) {
console.warn(`sio is not initialized`);
return;
}
// console.log("emit!")
this.socket.emit("request_message", [timestamp, newBuffer.buffer]);
} else if (this.setting.protocol === "rest") {
const restClient = new ServerRestClient(this.setting.serverUrl);
const res = await restClient.postVoice(timestamp, newBuffer.buffer);
if (res.byteLength < 128 * 2) {
this.listener.notifyException(
VOICE_CHANGER_CLIENT_EXCEPTION.ERR_REST_INVALID_RESPONSE,
`[REST] recevied data is too short ${res.byteLength}`
);
} else {
if (this.outputNode != null) {
this.outputNode.postReceivedVoice(res);
} else {
this.postReceivedVoice(res);
}
this.listener.notifyResponseTime(Date.now() - timestamp);
}
} else if (this.setting.protocol == "internal") {
if (!this.internalCallback) {
this.listener.notifyException(
VOICE_CHANGER_CLIENT_EXCEPTION.ERR_INTERNAL_AUDIO_PROCESS_CALLBACK_IS_NOT_INITIALIZED,
`[AudioWorkletNode] internal audio process callback is not initialized`
);
return;
}
// const res = await this.internalCallback.processAudio(newBuffer);
// if (res.length < 128 * 2) {
// return;
// }
// if (this.outputNode != null) {
// this.outputNode.postReceivedVoice(res.buffer);
// } else {
// this.postReceivedVoice(res.buffer);
// }
this.internalCallback.processAudio(newBuffer).then((res) => {
if (res.length < 128 * 2) {
return;
}
if (this.outputNode != null) {
this.outputNode.postReceivedVoice(res.buffer);
} else {
this.postReceivedVoice(res.buffer);
}
});
} else {
throw "unknown protocol";
}
};
// Worklet操作
configure = (setting: WorkletSetting) => {
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "config",
voice: new Float32Array(1),
numTrancateTreshold: setting.numTrancateTreshold,
volTrancateThreshold: setting.volTrancateThreshold,
volTrancateLength: setting.volTrancateLength,
};
this.port.postMessage(req);
};
start = async () => {
const p = new Promise<void>((resolve) => {
this.startPromiseResolve = resolve;
});
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "start",
voice: new Float32Array(1),
numTrancateTreshold: 0,
volTrancateThreshold: 0,
volTrancateLength: 0,
};
this.port.postMessage(req);
await p;
};
stop = async () => {
const p = new Promise<void>((resolve) => {
this.stopPromiseResolve = resolve;
});
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "stop",
voice: new Float32Array(1),
numTrancateTreshold: 0,
volTrancateThreshold: 0,
volTrancateLength: 0,
};
this.port.postMessage(req);
await p;
};
trancateBuffer = () => {
const req: VoiceChangerWorkletProcessorRequest = {
requestType: "trancateBuffer",
voice: new Float32Array(1),
numTrancateTreshold: 0,
volTrancateThreshold: 0,
volTrancateLength: 0,
};
this.port.postMessage(req);
};
startOutputRecording = () => {
this.recordingOutputChunk = [];
this.isOutputRecording = true;
};
stopOutputRecording = () => {
this.isOutputRecording = false;
const dataSize = this.recordingOutputChunk.reduce((prev, cur) => {
return prev + cur.length;
}, 0);
const samples = new Float32Array(dataSize);
let sampleIndex = 0;
for (let i = 0; i < this.recordingOutputChunk.length; i++) {
for (let j = 0; j < this.recordingOutputChunk[i].length; j++) {
samples[sampleIndex] = this.recordingOutputChunk[i][j];
sampleIndex++;
}
}
return samples;
};
}

View File

@ -1,18 +1,26 @@
// (★1) chunk sizeは 128サンプル, 256byte(int16)と定義。
// (★2) 256byte(最低バッファサイズ256から間引いた個数x2byte)をchunkとして管理。
// 24000sample -> 1sec, 128sample(1chunk) -> 5.333msec
// 187.5chunk -> 1sec
export const VoiceChangerType = {
"MMVCv15": "MMVCv15",
"MMVCv13": "MMVCv13",
MMVCv15: "MMVCv15",
MMVCv13: "MMVCv13",
"so-vits-svc-40": "so-vits-svc-40",
"DDSP-SVC": "DDSP-SVC",
"RVC": "RVC"
RVC: "RVC",
"Diffusion-SVC": "Diffusion-SVC",
Beatrice: "Beatrice",
LLVC: "LLVC",
WebModel: "WebModel",
EasyVC: "EasyVC",
} as const;
export type VoiceChangerType = (typeof VoiceChangerType)[keyof typeof VoiceChangerType];
} as const
export type VoiceChangerType = typeof VoiceChangerType[keyof typeof VoiceChangerType]
export const StaticModel = {
BeatriceJVS: "Beatrice-JVS",
} as const;
export type StaticModel = (typeof StaticModel)[keyof typeof StaticModel];
///////////////////////
// サーバセッティング
@ -20,289 +28,353 @@ export type VoiceChangerType = typeof VoiceChangerType[keyof typeof VoiceChanger
export const InputSampleRate = {
"48000": 48000,
"44100": 44100,
"24000": 24000
} as const
export type InputSampleRate = typeof InputSampleRate[keyof typeof InputSampleRate]
"24000": 24000,
} as const;
export type InputSampleRate = (typeof InputSampleRate)[keyof typeof InputSampleRate];
export const ModelSamplingRate = {
"48000": 48000,
"40000": 40000,
"32000": 32000
} as const
export type ModelSamplingRate = typeof InputSampleRate[keyof typeof InputSampleRate]
"32000": 32000,
} as const;
export type ModelSamplingRate = (typeof InputSampleRate)[keyof typeof InputSampleRate];
export const CrossFadeOverlapSize = {
"128": 128,
"256": 256,
"512": 512,
"1024": 1024,
"2048": 2048,
"4096": 4096,
} as const
export type CrossFadeOverlapSize = typeof CrossFadeOverlapSize[keyof typeof CrossFadeOverlapSize]
} as const;
export type CrossFadeOverlapSize = (typeof CrossFadeOverlapSize)[keyof typeof CrossFadeOverlapSize];
export const F0Detector = {
"dio": "dio",
"harvest": "harvest",
"crepe": "crepe",
"crepe_full": "crepe_full",
"crepe_tiny": "crepe_tiny",
} as const
export type F0Detector = typeof F0Detector[keyof typeof F0Detector]
dio: "dio",
harvest: "harvest",
crepe: "crepe",
crepe_full: "crepe_full",
crepe_tiny: "crepe_tiny",
rmvpe: "rmvpe",
rmvpe_onnx: "rmvpe_onnx",
fcpe: "fcpe",
} as const;
export type F0Detector = (typeof F0Detector)[keyof typeof F0Detector];
export const DiffMethod = {
"pndm": "pndm",
pndm: "pndm",
"dpm-solver": "dpm-solver",
} as const
export type DiffMethod = typeof DiffMethod[keyof typeof DiffMethod]
} as const;
export type DiffMethod = (typeof DiffMethod)[keyof typeof DiffMethod];
export const RVCModelType = {
"pyTorchRVC": "pyTorchRVC",
"pyTorchRVCNono": "pyTorchRVCNono",
"pyTorchRVCv2": "pyTorchRVCv2",
"pyTorchRVCv2Nono": "pyTorchRVCv2Nono",
"pyTorchWebUI": "pyTorchWebUI",
"pyTorchWebUINono": "pyTorchWebUINono",
"onnxRVC": "onnxRVC",
"onnxRVCNono": "onnxRVCNono",
} as const
export type RVCModelType = typeof RVCModelType[keyof typeof RVCModelType]
pyTorchRVC: "pyTorchRVC",
pyTorchRVCNono: "pyTorchRVCNono",
pyTorchRVCv2: "pyTorchRVCv2",
pyTorchRVCv2Nono: "pyTorchRVCv2Nono",
pyTorchWebUI: "pyTorchWebUI",
pyTorchWebUINono: "pyTorchWebUINono",
onnxRVC: "onnxRVC",
onnxRVCNono: "onnxRVCNono",
} as const;
export type RVCModelType = (typeof RVCModelType)[keyof typeof RVCModelType];
export const ServerSettingKey = {
"srcId": "srcId",
"dstId": "dstId",
"gpu": "gpu",
passThrough: "passThrough",
srcId: "srcId",
dstId: "dstId",
gpu: "gpu",
"crossFadeOffsetRate": "crossFadeOffsetRate",
"crossFadeEndRate": "crossFadeEndRate",
"crossFadeOverlapSize": "crossFadeOverlapSize",
crossFadeOffsetRate: "crossFadeOffsetRate",
crossFadeEndRate: "crossFadeEndRate",
crossFadeOverlapSize: "crossFadeOverlapSize",
"framework": "framework",
"onnxExecutionProvider": "onnxExecutionProvider",
framework: "framework",
onnxExecutionProvider: "onnxExecutionProvider",
"f0Factor": "f0Factor",
"f0Detector": "f0Detector",
"recordIO": "recordIO",
f0Factor: "f0Factor",
f0Detector: "f0Detector",
recordIO: "recordIO",
"enableServerAudio": "enableServerAudio",
"serverAudioStated": "serverAudioStated",
"serverAudioSampleRate": "serverAudioSampleRate",
"serverInputAudioSampleRate": "serverInputAudioSampleRate",
"serverOutputAudioSampleRate": "serverOutputAudioSampleRate",
"serverMonitorAudioSampleRate": "serverMonitorAudioSampleRate",
"serverInputAudioBufferSize": "serverInputAudioBufferSize",
"serverOutputAudioBufferSize": "serverOutputAudioBufferSize",
"serverInputDeviceId": "serverInputDeviceId",
"serverOutputDeviceId": "serverOutputDeviceId",
"serverMonitorDeviceId": "serverMonitorDeviceId",
"serverReadChunkSize": "serverReadChunkSize",
"serverInputAudioGain": "serverInputAudioGain",
"serverOutputAudioGain": "serverOutputAudioGain",
enableServerAudio: "enableServerAudio",
serverAudioStated: "serverAudioStated",
serverAudioSampleRate: "serverAudioSampleRate",
serverInputAudioSampleRate: "serverInputAudioSampleRate",
serverOutputAudioSampleRate: "serverOutputAudioSampleRate",
serverMonitorAudioSampleRate: "serverMonitorAudioSampleRate",
serverInputAudioBufferSize: "serverInputAudioBufferSize",
serverOutputAudioBufferSize: "serverOutputAudioBufferSize",
serverInputDeviceId: "serverInputDeviceId",
serverOutputDeviceId: "serverOutputDeviceId",
serverMonitorDeviceId: "serverMonitorDeviceId",
serverReadChunkSize: "serverReadChunkSize",
serverInputAudioGain: "serverInputAudioGain",
serverOutputAudioGain: "serverOutputAudioGain",
serverMonitorAudioGain: "serverMonitorAudioGain",
"tran": "tran",
"noiseScale": "noiseScale",
"predictF0": "predictF0",
"silentThreshold": "silentThreshold",
"extraConvertSize": "extraConvertSize",
"clusterInferRatio": "clusterInferRatio",
tran: "tran",
noiseScale: "noiseScale",
predictF0: "predictF0",
silentThreshold: "silentThreshold",
extraConvertSize: "extraConvertSize",
clusterInferRatio: "clusterInferRatio",
"indexRatio": "indexRatio",
"protect": "protect",
"rvcQuality": "rvcQuality",
"modelSamplingRate": "modelSamplingRate",
"silenceFront": "silenceFront",
"modelSlotIndex": "modelSlotIndex",
indexRatio: "indexRatio",
protect: "protect",
rvcQuality: "rvcQuality",
modelSamplingRate: "modelSamplingRate",
silenceFront: "silenceFront",
modelSlotIndex: "modelSlotIndex",
"useEnhancer": "useEnhancer",
"useDiff": "useDiff",
useEnhancer: "useEnhancer",
useDiff: "useDiff",
// "useDiffDpm": "useDiffDpm",
"diffMethod": "diffMethod",
"useDiffSilence": "useDiffSilence",
"diffAcc": "diffAcc",
"diffSpkId": "diffSpkId",
"kStep": "kStep",
"threshold": "threshold",
diffMethod: "diffMethod",
useDiffSilence: "useDiffSilence",
diffAcc: "diffAcc",
diffSpkId: "diffSpkId",
kStep: "kStep",
threshold: "threshold",
"inputSampleRate": "inputSampleRate",
"enableDirectML": "enableDirectML",
} as const
export type ServerSettingKey = typeof ServerSettingKey[keyof typeof ServerSettingKey]
speedUp: "speedUp",
skipDiffusion: "skipDiffusion",
inputSampleRate: "inputSampleRate",
enableDirectML: "enableDirectML",
} as const;
export type ServerSettingKey = (typeof ServerSettingKey)[keyof typeof ServerSettingKey];
export type VoiceChangerServerSetting = {
srcId: number,
dstId: number,
gpu: number,
passThrough: boolean;
srcId: number;
dstId: number;
gpu: number;
crossFadeOffsetRate: number,
crossFadeEndRate: number,
crossFadeOverlapSize: CrossFadeOverlapSize,
crossFadeOffsetRate: number;
crossFadeEndRate: number;
crossFadeOverlapSize: CrossFadeOverlapSize;
f0Factor: number
f0Detector: F0Detector // dio or harvest
recordIO: number // 0:off, 1:on
f0Factor: number;
f0Detector: F0Detector; // dio or harvest
recordIO: number; // 0:off, 1:on
enableServerAudio: number // 0:off, 1:on
serverAudioStated: number // 0:off, 1:on
serverAudioSampleRate: number
serverInputAudioSampleRate: number
serverOutputAudioSampleRate: number
serverMonitorAudioSampleRate: number
serverInputAudioBufferSize: number
serverOutputAudioBufferSize: number
serverInputDeviceId: number
serverOutputDeviceId: number
serverMonitorDeviceId: number
serverReadChunkSize: number
serverInputAudioGain: number
serverOutputAudioGain: number
enableServerAudio: number; // 0:off, 1:on
serverAudioStated: number; // 0:off, 1:on
serverAudioSampleRate: number;
serverInputAudioSampleRate: number;
serverOutputAudioSampleRate: number;
serverMonitorAudioSampleRate: number;
serverInputAudioBufferSize: number;
serverOutputAudioBufferSize: number;
serverInputDeviceId: number;
serverOutputDeviceId: number;
serverMonitorDeviceId: number;
serverReadChunkSize: number;
serverInputAudioGain: number;
serverOutputAudioGain: number;
serverMonitorAudioGain: number;
tran: number; // so-vits-svc
noiseScale: number; // so-vits-svc
predictF0: number; // so-vits-svc
silentThreshold: number; // so-vits-svc
extraConvertSize: number; // so-vits-svc
clusterInferRatio: number; // so-vits-svc
tran: number // so-vits-svc
noiseScale: number // so-vits-svc
predictF0: number // so-vits-svc
silentThreshold: number // so-vits-svc
extraConvertSize: number// so-vits-svc
clusterInferRatio: number // so-vits-svc
indexRatio: number; // RVC
protect: number; // RVC
rvcQuality: number; // 0:low, 1:high
silenceFront: number; // 0:off, 1:on
modelSamplingRate: ModelSamplingRate; // 32000,40000,48000
modelSlotIndex: number | StaticModel;
indexRatio: number // RVC
protect: number // RVC
rvcQuality: number // 0:low, 1:high
silenceFront: number // 0:off, 1:on
modelSamplingRate: ModelSamplingRate // 32000,40000,48000
modelSlotIndex: number,
useEnhancer: number// DDSP-SVC
useDiff: number// DDSP-SVC
useEnhancer: number; // DDSP-SVC
useDiff: number; // DDSP-SVC
// useDiffDpm: number// DDSP-SVC
diffMethod: DiffMethod, // DDSP-SVC
useDiffSilence: number// DDSP-SVC
diffAcc: number// DDSP-SVC
diffSpkId: number// DDSP-SVC
kStep: number// DDSP-SVC
threshold: number// DDSP-SVC
diffMethod: DiffMethod; // DDSP-SVC
useDiffSilence: number; // DDSP-SVC
diffAcc: number; // DDSP-SVC
diffSpkId: number; // DDSP-SVC
kStep: number; // DDSP-SVC
threshold: number; // DDSP-SVC
inputSampleRate: InputSampleRate
enableDirectML: number
}
speedUp: number; // Diffusion-SVC
skipDiffusion: number; // Diffusion-SVC 0:off, 1:on
inputSampleRate: InputSampleRate;
enableDirectML: number;
};
type ModelSlot = {
voiceChangerType: VoiceChangerType
name: string,
description: string,
credit: string,
termsOfUseUrl: string,
iconFile: string
speakers: { [key: number]: string }
}
slotIndex: number | StaticModel;
voiceChangerType: VoiceChangerType;
name: string;
description: string;
credit: string;
termsOfUseUrl: string;
iconFile: string;
speakers: { [key: number]: string };
};
export type RVCModelSlot = ModelSlot & {
modelFile: string
indexFile: string,
defaultIndexRatio: number,
defaultProtect: number,
defaultTune: number,
modelType: RVCModelType,
modelFile: string;
indexFile: string;
defaultIndexRatio: number;
defaultProtect: number;
defaultTune: number;
modelType: RVCModelType;
embChannels: number,
f0: boolean,
samplingRate: number
deprecated: boolean
}
embChannels: number;
f0: boolean;
samplingRate: number;
deprecated: boolean;
};
export type MMVCv13ModelSlot = ModelSlot & {
modelFile: string
configFile: string,
srcId: number
dstId: number
modelFile: string;
configFile: string;
srcId: number;
dstId: number;
samplingRate: number
speakers: { [key: number]: string }
}
samplingRate: number;
speakers: { [key: number]: string };
};
export type MMVCv15ModelSlot = ModelSlot & {
modelFile: string
configFile: string,
srcId: number
dstId: number
f0Factor: number
samplingRate: number
f0: { [key: number]: number }
}
modelFile: string;
configFile: string;
srcId: number;
dstId: number;
f0Factor: number;
samplingRate: number;
f0: { [key: number]: number };
};
export type SoVitsSvc40ModelSlot = ModelSlot & {
modelFile: string
configFile: string,
clusterFile: string,
dstId: number
modelFile: string;
configFile: string;
clusterFile: string;
dstId: number;
samplingRate: number
samplingRate: number;
defaultTune: number
defaultClusterInferRatio: number
noiseScale: number
speakers: { [key: number]: string }
}
defaultTune: number;
defaultClusterInferRatio: number;
noiseScale: number;
speakers: { [key: number]: string };
};
export type DDSPSVCModelSlot = ModelSlot & {
modelFile: string
configFile: string,
diffModelFile: string
diffConfigFile: string
dstId: number
modelFile: string;
configFile: string;
diffModelFile: string;
diffConfigFile: string;
dstId: number;
samplingRate: number
samplingRate: number;
defaultTune: number
enhancer: boolean
diffusion: boolean
acc: number
kstep: number
speakers: { [key: number]: string }
}
defaultTune: number;
enhancer: boolean;
diffusion: boolean;
acc: number;
kstep: number;
speakers: { [key: number]: string };
};
export type ModelSlotUnion = RVCModelSlot | MMVCv13ModelSlot | MMVCv15ModelSlot | SoVitsSvc40ModelSlot | DDSPSVCModelSlot
export type DiffusionSVCModelSlot = ModelSlot & {
modelFile: string;
dstId: number;
samplingRate: number;
defaultTune: number;
defaultKstep: number;
defaultSpeedup: number;
kStepMax: number;
nLayers: number;
nnLayers: number;
speakers: { [key: number]: string };
};
export type BeatriceModelSlot = ModelSlot & {
modelFile: string;
dstId: number;
speakers: { [key: number]: string };
};
export type LLVCModelSlot = ModelSlot & {
modelFile: string;
configFile: string;
speakers: { [key: number]: string };
};
export type WebModelSlot = ModelSlot & {
modelFile: string;
defaultTune: number;
modelType: RVCModelType;
f0: boolean;
samplingRate: number;
};
export type ModelSlotUnion = RVCModelSlot | MMVCv13ModelSlot | MMVCv15ModelSlot | SoVitsSvc40ModelSlot | DDSPSVCModelSlot | DiffusionSVCModelSlot | BeatriceModelSlot | LLVCModelSlot | WebModelSlot;
type ServerAudioDevice = {
kind: "audioinput" | "audiooutput",
index: number,
name: string
hostAPI: string
}
kind: "audioinput" | "audiooutput";
index: number;
name: string;
hostAPI: string;
};
export type ServerInfo = VoiceChangerServerSetting & {
// コンフィグ対象外 (getInfoで取得のみ可能な情報)
status: string
modelSlots: ModelSlotUnion[]
serverAudioInputDevices: ServerAudioDevice[]
serverAudioOutputDevices: ServerAudioDevice[]
sampleModels: RVCSampleModel[]
status: string;
modelSlots: ModelSlotUnion[];
serverAudioInputDevices: ServerAudioDevice[];
serverAudioOutputDevices: ServerAudioDevice[];
sampleModels: (RVCSampleModel | DiffusionSVCSampleModel)[];
gpus: {
id: number,
name: string,
memory: number,
}[]
id: number;
name: string;
memory: number;
}[];
maxInputLength: number; // MMVCv15
voiceChangerParams: {
model_dir: string;
};
};
}
export type SampleModel = {
id: string;
voiceChangerType: VoiceChangerType;
lang: string;
tag: string[];
name: string;
modelUrl: string;
termsOfUseUrl: string;
icon: string;
credit: string;
description: string;
sampleRate: number;
modelType: string;
f0: boolean;
};
export type RVCSampleModel = SampleModel & {
indexUrl: string;
featureUrl: string;
};
export type RVCSampleModel = {
id: string
name: string
modelUrl: string
indexUrl: string
featureUrl: string
termsOfUseUrl: string
credit: string
description: string
lang: string
tag: string[]
icon: string
f0: boolean
sampleRate: number
modelType: string
}
export type DiffusionSVCSampleModel = SampleModel & {
numOfDiffLayers: number;
numOfNativeLayers: number;
maxKStep: number;
};
export const DefaultServerSetting: ServerInfo = {
// VC Common
// VC Common
passThrough: false,
inputSampleRate: 48000,
crossFadeOffsetRate: 0.0,
@ -325,15 +397,15 @@ export const DefaultServerSetting: ServerInfo = {
serverReadChunkSize: 256,
serverInputAudioGain: 1.0,
serverOutputAudioGain: 1.0,
serverMonitorAudioGain: 1.0,
// VC Specific
srcId: 0,
dstId: 1,
gpu: 0,
f0Factor: 1.0,
f0Detector: F0Detector.dio,
f0Detector: F0Detector.rmvpe_onnx,
tran: 0,
noiseScale: 0,
@ -360,94 +432,105 @@ export const DefaultServerSetting: ServerInfo = {
kStep: 120,
threshold: -45,
speedUp: 10,
skipDiffusion: 1,
enableDirectML: 0,
//
//
status: "ok",
modelSlots: [],
serverAudioInputDevices: [],
serverAudioOutputDevices: []
}
serverAudioOutputDevices: [],
maxInputLength: 128 * 2048,
voiceChangerParams: {
model_dir: "",
},
};
///////////////////////
// Workletセッティング
///////////////////////
export type WorkletSetting = {
numTrancateTreshold: number,
volTrancateThreshold: number,
volTrancateLength: number
}
numTrancateTreshold: number;
volTrancateThreshold: number;
volTrancateLength: number;
};
///////////////////////
// Worklet Nodeセッティング
///////////////////////
export const Protocol = {
"sio": "sio",
"rest": "rest",
} as const
export type Protocol = typeof Protocol[keyof typeof Protocol]
sio: "sio",
rest: "rest",
internal: "internal",
} as const;
export type Protocol = (typeof Protocol)[keyof typeof Protocol];
export const SendingSampleRate = {
"48000": 48000,
"44100": 44100,
"24000": 24000
} as const
export type SendingSampleRate = typeof SendingSampleRate[keyof typeof SendingSampleRate]
"24000": 24000,
} as const;
export type SendingSampleRate = (typeof SendingSampleRate)[keyof typeof SendingSampleRate];
export const DownSamplingMode = {
"decimate": "decimate",
"average": "average"
} as const
export type DownSamplingMode = typeof DownSamplingMode[keyof typeof DownSamplingMode]
decimate: "decimate",
average: "average",
} as const;
export type DownSamplingMode = (typeof DownSamplingMode)[keyof typeof DownSamplingMode];
export type WorkletNodeSetting = {
serverUrl: string,
protocol: Protocol,
sendingSampleRate: SendingSampleRate,
inputChunkNum: number,
downSamplingMode: DownSamplingMode,
}
serverUrl: string;
protocol: Protocol;
sendingSampleRate: SendingSampleRate;
inputChunkNum: number;
downSamplingMode: DownSamplingMode;
};
///////////////////////
// クライアントセッティング
///////////////////////
export const SampleRate = {
"48000": 48000,
} as const
export type SampleRate = typeof SampleRate[keyof typeof SampleRate]
} as const;
export type SampleRate = (typeof SampleRate)[keyof typeof SampleRate];
export type VoiceChangerClientSetting = {
audioInput: string | MediaStream | null,
sampleRate: SampleRate, // 48000Hz
echoCancel: boolean,
noiseSuppression: boolean,
noiseSuppression2: boolean
audioInput: string | MediaStream | null;
sampleRate: SampleRate; // 48000Hz
echoCancel: boolean;
noiseSuppression: boolean;
noiseSuppression2: boolean;
inputGain: number
outputGain: number
}
inputGain: number;
outputGain: number;
monitorGain: number;
passThroughConfirmationSkip: boolean;
};
///////////////////////
// Client セッティング
///////////////////////
export type ClientSetting = {
workletSetting: WorkletSetting
workletNodeSetting: WorkletNodeSetting
voiceChangerClientSetting: VoiceChangerClientSetting
}
workletSetting: WorkletSetting;
workletNodeSetting: WorkletNodeSetting;
voiceChangerClientSetting: VoiceChangerClientSetting;
};
export const DefaultClientSettng: ClientSetting = {
workletSetting: {
// numTrancateTreshold: 512 * 2,
numTrancateTreshold: 100,
volTrancateThreshold: 0.0005,
volTrancateLength: 32
volTrancateLength: 32,
},
workletNodeSetting: {
serverUrl: "",
protocol: "sio",
sendingSampleRate: 48000,
inputChunkNum: 48,
downSamplingMode: "average"
inputChunkNum: 192,
downSamplingMode: "average",
},
voiceChangerClientSetting: {
audioInput: null,
@ -456,10 +539,11 @@ export const DefaultClientSettng: ClientSetting = {
noiseSuppression: false,
noiseSuppression2: false,
inputGain: 1.0,
outputGain: 1.0
}
}
outputGain: 1.0,
monitorGain: 1.0,
passThroughConfirmationSkip: false,
},
};
////////////////////////////////////
// Exceptions
@ -468,36 +552,34 @@ export const VOICE_CHANGER_CLIENT_EXCEPTION = {
ERR_SIO_CONNECT_FAILED: "ERR_SIO_CONNECT_FAILED",
ERR_SIO_INVALID_RESPONSE: "ERR_SIO_INVALID_RESPONSE",
ERR_REST_INVALID_RESPONSE: "ERR_REST_INVALID_RESPONSE",
ERR_MIC_STREAM_NOT_INITIALIZED: "ERR_MIC_STREAM_NOT_INITIALIZED"
} as const
export type VOICE_CHANGER_CLIENT_EXCEPTION = typeof VOICE_CHANGER_CLIENT_EXCEPTION[keyof typeof VOICE_CHANGER_CLIENT_EXCEPTION]
ERR_MIC_STREAM_NOT_INITIALIZED: "ERR_MIC_STREAM_NOT_INITIALIZED",
ERR_INTERNAL_AUDIO_PROCESS_CALLBACK_IS_NOT_INITIALIZED: "ERR_INTERNAL_AUDIO_PROCESS_CALLBACK_IS_NOT_INITIALIZED",
} as const;
export type VOICE_CHANGER_CLIENT_EXCEPTION = (typeof VOICE_CHANGER_CLIENT_EXCEPTION)[keyof typeof VOICE_CHANGER_CLIENT_EXCEPTION];
////////////////////////////////////
// indexedDB
////////////////////////////////////
export const INDEXEDDB_DB_APP_NAME = "INDEXEDDB_KEY_VOICE_CHANGER"
export const INDEXEDDB_DB_NAME = "INDEXEDDB_KEY_VOICE_CHANGER_DB"
export const INDEXEDDB_KEY_CLIENT = "INDEXEDDB_KEY_VOICE_CHANGER_LIB_CLIENT"
export const INDEXEDDB_KEY_SERVER = "INDEXEDDB_KEY_VOICE_CHANGER_LIB_SERVER"
export const INDEXEDDB_KEY_MODEL_DATA = "INDEXEDDB_KEY_VOICE_CHANGER_LIB_MODEL_DATA"
export const INDEXEDDB_DB_APP_NAME = "INDEXEDDB_KEY_VOICE_CHANGER";
export const INDEXEDDB_DB_NAME = "INDEXEDDB_KEY_VOICE_CHANGER_DB";
export const INDEXEDDB_KEY_CLIENT = "INDEXEDDB_KEY_VOICE_CHANGER_LIB_CLIENT";
export const INDEXEDDB_KEY_SERVER = "INDEXEDDB_KEY_VOICE_CHANGER_LIB_SERVER";
export const INDEXEDDB_KEY_MODEL_DATA = "INDEXEDDB_KEY_VOICE_CHANGER_LIB_MODEL_DATA";
// ONNX
export type OnnxExporterInfo = {
"status": string
"path": string
"filename": string
}
status: string;
path: string;
filename: string;
};
// Merge
export type MergeElement = {
filename: string
strength: number
}
slotIndex: number;
strength: number;
};
export type MergeModelRequest = {
voiceChangerType: VoiceChangerType
command: "mix",
files: MergeElement[]
}
voiceChangerType: VoiceChangerType;
command: "mix";
files: MergeElement[];
};

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