blechmusikの日記

キー・カスタマイズ・ソフトウェア "DvorakJ" の覚え書きをはじめとして様々なことを書いています。

Emacs の起動時と終了時に「“プロ生ちゃん”のシステムボイス」音源を再生する

後日追記分

ワードをもとに再生する音源を取得する処理を実装した。詳しくは【新版】のエントリーを参照してほしい。
【新版】Emacs の起動時と終了時に「“プロ生ちゃん”のシステムボイス」音源を再生する - blechmusikの日記



上坂すみれが声を務める“プロ生ちゃん”のシステムボイスが無償公開 - 窓の杜に触れて、Emacs の起動時と終了時に音源を再生することを思いついた。さまざまな音源が配布されているが、この中から挨拶の類いのメッセージを選び、使ってみよう。

 以下の2点をあらかじめ断っておきたい。

  1. 圧縮ファイルを展開した音源ファイル一式は ~/sound/ 以下にディレクトリの構造を保ったまま置いている
  2. Emacs は Windows 環境で動作する gnupack の NTEmacs である

 ところで音源を再生するにはどうすればよいのだろうか。 Emacs から音源を直接再生する手順も、そして (Vista 以降の) Windowsで他の音楽再生アプリケーションを使用せずに音源を再生する方法も私は分からなかった。

 そこで AutoHotkey の soundplay コマンドを使って指定したメディアファイルを再生するスクリプトを作成し、実行バイナリ化してみる。

for i,v in get_command_argument() {
    filename := slash_to_backslash(v)
    SoundPlay, %Filename%, 1
}
ExitApp
return


slash_to_backslash(string){
    StringReplace, string, string, /, \
    return string
}


get_command_argument(){
    global
    local arg_list := []
    
    Loop,100
    {
        if ("" != Trim(%A_Index%)){
            arg_list.insert(%A_Index%)
        }
    }
    
    return arg_list
}

 Emacs 側から渡されるパスにスラッシュが含まれている可能性を想定して、スラッシュをバックスラッシュに書き換えることにした。

 次に行うのは Emacs lisp を使って上記のAutoHotkeyスクリプトを実行し、音源を再生することだ。file-truename関数を使ってパスを展開することにしたから、これによってAutoHotkey のスクリプト側でシンボリック・リンクを張ったファイルについても問題なく取り扱える。

(defvar soundplay-command (file-truename "~/soundplay/soundplay.exe"))

(defun play-sound (sound-file)  (with-temp-buffer
    (start-process "sync" nil
                   soundplay-command
                   sound-file
                   )))

;; はじめるよ
(play-sound (file-truename "~/sound/kei_voice_02/kei_voice_055.wav"))

 こうして「はじめるよ」と語る音源を再生することができた。
 だが、これでは Emacs の終了時に音源を再生し続けることができない。start-process の実行中にEmacsを終了させるとそこで音源の再生が中断してしまうのだ。この問題に対処するには start-process ではなく start-process-shell-command を使うことになる。

(defun play-sound (sound-file)
  (start-process-shell-command "async"
                               nil
                               soundplay-command
                               sound-file))

 これで音源を再生する準備が整った。

 残された作業は再生対象の音源を充実させることと、Emacs の起動時と終了時に音源を再生するようフックを設定することだ。音源の充実としていくつかの音源をまず用意し、それに加えて、特定の時間帯のみ利用できる音源も用意する。幸いなことに、朝昼夜の挨拶の音源が存在するから、これを利用しよう。そしてこのように用意した音源からどれか一つをその都度ランダムに選び出せばよいだろう。

(setq sound-directory "~/sound/"
      sound-file-good-morning '(
                                ;; おはよう
                                "kei_voice_01/kei_voice_008.wav"
                                )
      sound-file-good-afternoon '(
                                  ;; こんにちは
                                  "kei_voice_01/kei_voice_009.wav"
                                  )
      sound-file-good-evening '(
                                ;; こんばんは
                                "kei_voice_01/kei_voice_010.wav"
                                )
      sound-file-good-night '(
                              ;; おやすみ
                              "kei_voice_01/kei_voice_011.wav"
                              )
      sound-files-startup '(
                            ;; はじめるよ
                            "kei_voice_02/kei_voice_055.wav"
                            ;; スタート
                            "kei_voice_02/kei_voice_056.wav"
                            )
      sound-files-kill '(
                         ;; またきてね
                         "kei_voice_01/kei_voice_018.wav"

                         ;; 終了
                         "kei_voice_02/kei_voice_060.wav"
                         ;; 終わりだよ
                         "kei_voice_02/kei_voice_061_a.wav"
                         ;; 終わりだよ
                         "kei_voice_02/kei_voice_061_b.wav"

                         ;; はい終了
                         "kei_voice_03a/kei_voice_03/kei_voice_081.wav"
                         ))

(defun play-sound-startup ()
  (play-sound (sound-file-startup)))
      
(defun play-sound-kill ()
  (play-sound (sound-file-kill)))


(lexical-let ((hour (string-to-number (format-time-string "%H" (current-time)))))
  (labels ((get-wav-filename (files)
                             (file-truename (concat sound-directory
                                                    (nth (random (length files)) files)))))
    ;;  5時~9時: +おはよう
    ;; 11時~14時: +こんにちは
    ;; 20時~ 3時: +こんばんは
    (defun sound-file-startup ()
      (lexical-let ((sound-files
                     (append sound-files-startup
                             (cond
                              ((and (<= 5 hour) (< hour 9)) sound-file-good-morning)
                              ((and (<= 11 hour) (< hour 14)) sound-file-good-afternoon)
                              ((or (<= 20 hour) (< hour 3)) sound-file-good-evening)
                              (t nil)))))
        (get-wav-filename sound-files)))

    ;; 20時~: +おやすみ
    (defun sound-file-kill ()
      (lexical-let ((sound-files
                     (append sound-files-kill
                             (cond
                              ((<= 20 hour) sound-file-good-night)
                              (t nil)))))
        (get-wav-filename sound-files)))
    ))

 フックの処理としてEmacs の起動時向けにはemacs-startup-hookを、Emacs の終了時向けにはkill-emacs-hookを使う。注意しなければならないのは、 音源を再生してからEmacs を終了するまでの間には少々待機時間を設けねばならないことだ。待機時間を設けないと、終了時に音源を再生しようとする前に Emacs 本体が終了してしまう。ここでは0.1秒間待機することにした。

;; (play-sound-startup)
(add-hook 'emacs-startup-hook 'play-sound-startup)

;; (play-sound-kill)
(add-hook 'kill-emacs-hook (lambda ()
                             (play-sound-kill)
                             (sleep-for 0.1)
                             ))

これで設定が完了した。ここまでのemacs lisp の設定を纏めるとこうなる。

(defvar soundplay-command (file-truename "~/soundplay/soundplay.exe"))

(defun play-sound (sound-file)
  (start-process-shell-command "async"
                               nil
                               soundplay-command
                               sound-file))

(setq sound-directory "~/sound/"
      sound-file-good-morning '(
                                ;; おはよう
                                "kei_voice_01/kei_voice_008.wav"
                                )
      sound-file-good-afternoon '(
                                  ;; こんにちは
                                  "kei_voice_01/kei_voice_009.wav"
                                  )
      sound-file-good-evening '(
                                ;; こんばんは
                                "kei_voice_01/kei_voice_010.wav"
                                )
      sound-file-good-night '(
                              ;; おやすみ
                              "kei_voice_01/kei_voice_011.wav"
                              )
      sound-files-startup '(
                            ;; はじめるよ
                            "kei_voice_02/kei_voice_055.wav"
                            ;; スタート
                            "kei_voice_02/kei_voice_056.wav"
                            )
      sound-files-kill '(
                         ;; またきてね
                         "kei_voice_01/kei_voice_018.wav"

                         ;; 終了
                         "kei_voice_02/kei_voice_060.wav"
                         ;; 終わりだよ
                         "kei_voice_02/kei_voice_061_a.wav"
                         ;; 終わりだよ
                         "kei_voice_02/kei_voice_061_b.wav"

                         ;; はい終了
                         "kei_voice_03a/kei_voice_03/kei_voice_081.wav"
                         ))



(defun play-sound-startup ()
  (play-sound (sound-file-startup)))
      
(defun play-sound-kill ()
  (play-sound (sound-file-kill)))


(lexical-let ((hour (string-to-number (format-time-string "%H" (current-time)))))
  (labels ((get-wav-filename (files)
                             (file-truename (concat sound-directory
                                                    (nth (random (length files)) files)))))
    ;;  5時~9時: +おはよう
    ;; 11時~14時: +こんにちは
    ;; 20時~ 3時: +こんばんは
    (defun sound-file-startup ()
      (lexical-let ((sound-files
                     (append sound-files-startup
                             (cond
                              ((and (<= 5 hour) (< hour 9)) sound-file-good-morning)
                              ((and (<= 11 hour) (< hour 14)) sound-file-good-afternoon)
                              ((or (<= 20 hour) (< hour 3)) sound-file-good-evening)
                              (t nil)))))
        (get-wav-filename sound-files)))

    ;; 20時~: +おやすみ
    (defun sound-file-kill ()
      (lexical-let ((sound-files
                     (append sound-files-kill
                             (cond
                              ((<= 20 hour) sound-file-good-night)
                              (t nil)))))
        (get-wav-filename sound-files)))
    ))


;; (play-sound-startup)
(add-hook 'emacs-startup-hook 'play-sound-startup)

;; (play-sound-kill)
(add-hook 'kill-emacs-hook (lambda ()
                             (play-sound-kill)
                             (sleep-for 0.1)
                             ))

 このエントリーでは Emacs の起動時と終了時に「“プロ生ちゃん”のシステムボイス」音源を再生する手順を説明した。このエントリーでは省略したが、任意のプログラムのコンパイルや実行、デバッグといった場面において当該音源を活用できるのはいうまでもない。配布されている音源を活用してよりよい開発環境を構築したいものだ。

 なお今回配布された音源には、アルファベット以外のキーの読み上げは収録されているが、それとは対象的にアルファベットの部分は一切収録されていない。そして、カットやコピー、ペーストといった、キーを複数個組み合わせて行う一般的な処理の読み上げも収録されていない。これらが補完されればより利用しやすい音源集となるだろう。