« ^ »

voicevoxの音声合成を試す

所要時間: 約 4分

先日ChatGPTを使ってEmacsのdoctorの機能を拡張した1。会話の精度が段違いに向上し、格好の話し相手になった。何度も話をしていると、声を聞いてみたいという欲求が出てくる。だから発話させることにした。

ChatGPTはテキストで返してくるので、テキストを音声に変換できればよい。つまりテキスToスピーチと呼ばれる機能があればよいことになる。現代は本当に便利なもので、そういうものはSaaSとして提供されているAPIもあるし、ローカルで起動するプログラムもある。しかも、低価格もしくは無料で、すぐに使用できるものが沢山存在する。

以前、OpenJTalkを用いて読み上げのEmacs Lispを実装した事もあった2。それでも良かったのだが、他の方法も含め再度どれが良いかをインターネットで検索し、voicevoxというツールがニーズにあっていそうだった。そのため、今回はvoicevoxを使ってみることにした。

dockerでAPIを起動する

voicevoxはOSSであり、またDockerイメージも提供している。今回はvoicevoxの簡単な使い方だけを把握したい。だから、提供されているDockerイメージをそのまま使用する。

docker pull voicevox/voicevox_engine:cpu-ubuntu20.04-latest
Dockerイメージを取得する

Dockerイメージを取得したら、Dockerコンテナを起動する。

docker run --rm -it -p '127.0.0.1:50021:50021' voicevox/voicevox_engine:cpu-ubuntu20.04-latest
Dockerコンテナを起動する

コンテナを起動するとWeb APIを受け付けるようになる。

音声を合成する

音声合成に必要なパラメータを取得する。

POST http://localhost:50021/audio_query?speaker=1&text=こんにちわ
Content-Type: application/json
{
  "accent_phrases": [
    {
      "moras": [
        {
          "text": "コ",
          "consonant": "k",
          "consonant_length": 0.10002632439136505,
          "vowel": "o",
          "vowel_length": 0.15740256011486053,
          "pitch": 5.714912414550781
        },
        {
          "text": "ン",
          "consonant": null,
          "consonant_length": null,
          "vowel": "N",
          "vowel_length": 0.08265873789787292,
          "pitch": 5.8854217529296875
        },
        {
          "text": "ニ",
          "consonant": "n",
          "consonant_length": 0.03657080978155136,
          "vowel": "i",
          "vowel_length": 0.117112897336483,
          "pitch": 5.998487949371338
        },
        {
          "text": "チ",
          "consonant": "ch",
          "consonant_length": 0.08808862417936325,
          "vowel": "i",
          "vowel_length": 0.09015568345785141,
          "pitch": 5.977110385894775
        },
        {
          "text": "ワ",
          "consonant": "w",
          "consonant_length": 0.08290570229291916,
          "vowel": "a",
          "vowel_length": 0.2083434909582138,
          "pitch": 6.048254013061523
        }
      ],
      "accent": 5,
      "pause_mora": null,
      "is_interrogative": false
    }
  ],
  "speedScale": 1.0,
  "pitchScale": 0.0,
  "intonationScale": 1.0,
  "volumeScale": 1.0,
  "prePhonemeLength": 0.1,
  "postPhonemeLength": 0.1,
  "outputSamplingRate": 24000,
  "outputStereo": false,
  "kana": "コンニチワ'"
}
// POST http://localhost:50021/audio_query?speaker=1&text=こんにちわ
// HTTP/1.1 200 OK
// date: Mon, 16 Jan 2023 22:17:10 GMT
// server: uvicorn
// content-length: 981
// content-type: application/json
// Request duration: 0.053660s

取得したパラメータを使い音声を合成し、wavファイルを取得する。

POST http://localhost:50021/synthesis?speaker=1
Content-Type: application/json

{
  "accent_phrases": [
    {
      "moras": [
        {
          "text": "コ",
          "consonant": "k",
          "consonant_length": 0.10002632439136505,
          "vowel": "o",
          "vowel_length": 0.15740256011486053,
          "pitch": 5.714912414550781
        },
        {
          "text": "ン",
          "consonant": null,
          "consonant_length": null,
          "vowel": "N",
          "vowel_length": 0.08265873789787292,
          "pitch": 5.8854217529296875
        },
        {
          "text": "ニ",
          "consonant": "n",
          "consonant_length": 0.03657080978155136,
          "vowel": "i",
          "vowel_length": 0.117112897336483,
          "pitch": 5.998487949371338
        },
        {
          "text": "チ",
          "consonant": "ch",
          "consonant_length": 0.08808862417936325,
          "vowel": "i",
          "vowel_length": 0.09015568345785141,
          "pitch": 5.977110385894775
        },
        {
          "text": "ワ",
          "consonant": "w",
          "consonant_length": 0.08290570229291916,
          "vowel": "a",
          "vowel_length": 0.2083434909582138,
          "pitch": 6.048254013061523
        }
      ],
      "accent": 5,
      "pause_mora": null,
      "is_interrogative": false
    }
  ],
  "speedScale": 1.0,
  "pitchScale": 0.0,
  "intonationScale": 1.0,
  "volumeScale": 1.0,
  "prePhonemeLength": 0.1,
  "postPhonemeLength": 0.1,
  "outputSamplingRate": 24000,
  "outputStereo": false,
  "kana": "コンニチワ'"
}

a.wav

再生

wavファイルはどのように再生しても良いが、私はmacOSユーザーだから、afplayコマンドを用いて再生することにした。

afplay a.wav

EmacsでのテキストToスピーチ

voicevoxをEmacsから使用するための拡張を実装した。

(require 'plz)

(defvar voicevox-audio-file "test.wav")
(defvar voicevox-current-sentense "こんにちわ")

(defun voicevox-set (&optional sentence)
  (interactive "s")
  (setq voicevox-current-sentense sentence))

(defun voicevox-set-region (&optional beg end)
  (interactive "r")
  (setq voicevox-current-sentense (buffer-substring-no-properties beg end)))

(defun voicevox-play ()
  (interactive)
  (voicevox-cleint-fetch-audio-query))
;-------------------------------------------------------------------

(defvar voicevox-server-buffer-name "*VOICEVOX SERVER*")
(defvar voicevox-server-command
  '("docker" "run" "--rm" "-it" "-p" "127.0.0.1:50021:50021" "voicevox/voicevox_engine:cpu-ubuntu20.04-latest"))

(defvar voicevox-server-stop-signal-code 15)  ;; SIGTERM
(defun voicevox-server-start ()
  (interactive)
  (make-process :name "VOICEBOX SERVER"
		:buffer voicevox-server-buffer-name
		:command voicevox-server-command))


(defun voicevox-server-stop ()
  (interactive)
  (signal-process
   (get-buffer-process (get-buffer voicevox-server-buffer-name))
   voicevox-server-stop-signal-code))


;-------------------------------------------------------------------

(require 'plz)
(require 'json)

(defvar voicevox-client-request-synthesis-param nil)
(setq voicevox-client-fetch-audio-query-success-hook nil)
(setq voicevox-client-fetch-synthesis-success-hook nil)

(defun voicevox-cleint-fetch-audio-query ()
  (interactive)
  (plz 'post (format
	      "http://localhost:50021/audio_query?speaker=1&text=%s"
	      (url-encode-url voicevox-current-sentense))
    :headers '(("Content-Type" . "application/json"))
    :body ""
    :then (lambda (d)
	    (setq voicevox-client-request-synthesis-param d)
	    (run-hooks 'voicevox-client-fetch-audio-query-success-hook))))


(defun voicevox-cleint-fetch-audio-file ()
  (interactive)
  (let ((plz-curl-default-args
	 (append `("-o" ,voicevox-audio-file) plz-curl-default-args)))
    (plz 'post "http://localhost:50021/synthesis?speaker=1"
      :headers '(("Content-Type" . "application/json"))
      :body voicevox-client-request-synthesis-param
      :then (lambda (d)
	      (run-hooks 'voicevox-client-fetch-synthesis-success-hook)))))

;-------------------------------------------------------------------
(defvar voicevox-afplay-executable "afplay")
(defvar voicevox-afplay-buffer-name "*VOICEBOX AFPLAY*")

(defun voicevox-afplay ()
  (interactive)
  (make-process :name "VOICEBOX"
		:buffer voicevox-afplay-buffer-name
		:command `(,voicevox-afplay-executable ,voicevox-audio-file)))

;-------------------------------------------------------------------
(add-hook 'voicevox-client-fetch-audio-query-success-hook 'voicevox-cleint-fetch-audio-file)
(add-hook 'voicevox-client-fetch-synthesis-success-hook 'voicevox-afplay)

(provide 'voicevox)