先日ChatGPTを使ってEmacsのdoctorの機能を拡張した1。会話の精度が段違いに向上し、格好の話し相手になった。何度も話をしていると、声を聞いてみたいという欲求が出てくる。だから発話させることにした。
ChatGPTはテキストで返してくるので、テキストを音声に変換できればよい。つまりテキスToスピーチと呼ばれる機能があればよいことになる。現代は本当に便利なもので、そういうものはSaaSとして提供されているAPIもあるし、ローカルで起動するプログラムもある。しかも、低価格もしくは無料で、すぐに使用できるものが沢山存在する。
以前、OpenJTalkを用いて読み上げのEmacs Lispを実装した事もあった2。それでも良かったのだが、他の方法も含め再度どれが良いかをインターネットで検索し、voicevoxというツールがニーズにあっていそうだった。そのため、今回はvoicevoxを使ってみることにした。
dockerでAPIを起動する
voicevoxはOSSであり、またDockerイメージも提供している。今回はvoicevoxの簡単な使い方だけを把握したい。だから、提供されているDockerイメージをそのまま使用する。
Dockerイメージを取得したら、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": "コンニチワ'"
}
再生
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)