Pythonから音声ファイルを割り込みで再生させる

Rasberry PiでPython使って音声を再生するプログラムを組んだのですが、
なかなかに苦戦したのでその道筋を示します。

仕様としては、イベントが発生したらイベントに応じた音声を再生して、
音声再生中に別のイベントが発生したら、再生中の音声を停止して新しいイベントの音声を再生するといった感じです。

これが後で効いてきます。

# まずは王道のPyAudio
とりあえず「Python 音声 再生」みたいにググッたらどうやら[PyAudio](http://people.csail.mit.edu/hubert/pyaudio/)とやらがよく使われてるらしいので、こういつから試してみます。

インストールは公式にしたがって
`apt-get install python-pyaudio python3-pyaudio`
Pythonのバージョンは2.7.9なのでpython3-pyaudioはいらない気がします。

そして[参考サイト](https://fififactory.com/2015/04/29/python-python%E3%81%A7%E9%9F%B3%E6%A5%BD%E5%86%8D%E7%94%9F-wave-pyaudio/)を見ながら音声再生用のクラスを実装してみます

```py:audio_player.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import pyaudio
import wave
from time import sleep

class AudioPlayer:
    """ A Class For Playing Audio """

    def __init__(self):
        self.audio_file = ""

    def setAudioFile(self, audio_file):
        self.audio_file = audio_file

    def playAudio(self):
        if(self.audio_file == ""):
            return
        self.wf = wave.open(self.audio_file, "rb")
        p = pyaudio.PyAudio()
        stream = p.open(format=p.get_format_from_width(self.wf.getsampwidth()),
                channels=self.wf.getnchannels(),
                rate=self.wf.getframerate(),
                output=True,
                stream_callback=self.callback)
        stream.start_stream()

        while stream.is_active():
            sleep(0.1)

        stream.stop_stream()
        stream.close()
        self.wf.close()

        p.terminate()


    def callback(self, in_data, frame_count, time_info, status):
        data = self.wf.readframes(frame_count)
        return (data, pyaudio.paContinue)
        
```

## やめられない、止まらない
こうして無事に音声の再生には成功しましたが、お察しの通り問題が。
**止められねぇ...**
純粋に私のスキル不足かもしれませんが、割り込みで再生を止める方法が分かりませんでした。
プロセスとか覗いたらいけるのだろうか。

# Linuxにやらせてみよう

ここでちょっと視点を変えて、Pythonから音声を再生するのでなく、音声再生用のコマンドをPythonから実行して適宜中断させるとかどうだろうとか思ったわけです。

## aplay お前もか…
というわけで次はaplayを試します。`aplay hoge.wav`で再生できるからお手軽ですね。
aplayコマンドを実行するよう、さっきのクラスを書き換えます。

```py:audio_player.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import subprocess

class AudioPlayer:
    """ A Class For Playing Audio """

    def __init__(self):
        self.audio_file = ""
        self.is_playing = False

    def setAudioFile(self, audio_file):
        self.audio_file = audio_file

    def playAudio(self):
        if(self.audio_file == ""):
            return
        subprocess.call(["aplay", self.audio_file])
```

commandモジュールは非推奨なのでsubprocessモジュールを使います。
よし、これで再生はできる、中断するにはどうするのかなと`aplay --help`を見てみると......無いですね。
aplayの再生を止めるにはCtl+CかPID調べてkillするかしか無いみたいですね。これはいかんでしょ。

## そしてmpcへ...
いや、方向性は間違っていないはずだと信じてさらに調べると[mpc](http://www.multiprecision.org/index.php?prog=mpc)にたどり着きました。
[このページ](http://www.yam-web.net/raspberry-pi/music.html)の下のほうに書いてある内容を見る限り、音声を中断させるコマンドもある。いけるやん!

というわけでまたクラスを書き換えます。
といってもsubprocess.callを使うのは変わりません。

```py:audio_player.py
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import subprocess

class AudioPlayer:
    """ A Class For Playing Audio """

    def __init__(self):
        self.audio_file = ""
        self.is_playing = False

    def setAudioFile(self, audio_file):
        self.audio_file = audio_file

    def playAudio(self):
        if(self.audio_file == ""):
            return
        print 'play ' + self.audio_file
        subprocess.call(["mpc", "stop"])   #音声停止
        subprocess.call(["mpc", "clear"])  #プレイリストのクリア 
        subprocess.call(["mpc", "update"]) #音声ファイルの読み込み
        subprocess.call(["mpc", "add", self.audio_file]) #プレイリストに追加
        subprocess.call(["mpc", "play"]) #再生
```

これでテストしてみると...やった、音声が中断されて新しい音声が再生されたぞ!
これで勝つる!

# 参考ページ
- https://fififactory.com/2015/04/29/python-python%E3%81%A7%E9%9F%B3%E6%A5%BD%E5%86%8D%E7%94%9F-wave-pyaudio/
- http://www.yam-web.net/raspberry-pi/music.html
- http://qiita.com/Kodaira_/items/43e0d850c1c54038a7c9