以前作ったVoiceText Web APIをHaskellから呼び出すためのライブラリを使って、Haskellプログラムで音声データの再生を試してみた。
- GitHub: Network.VoiceText
- Qiita: VoiceText Web API の Haskell 用ライブラリを作った
Sound.ALUTというのを使うとよさそう。
インストール
Sound.ALUT と 拙作 Network.VoiceText を cabal sandbox 環境にインストールする。
cabal sandbox 環境のセットアップ
> mkdir voicetext
> cd voicetext
> cabal sandvox init
> cabal init
> vi voicetext.cabal
voicetext.cabal はこんな感じに編集する。(一部抜粋)
executable voicetext
main-is: Main.hs
build-depends: base >=4.7 && <4.8
, network-voicetext >= 0.0
, bytestring >=0.10
, OpenAL >=1.6
, ALUT >=2.3
依存ライブラリのインストール
> cabal install network-voicetext-0.0.0.1.tar.gz
> cabal install --only-dependencies
Resolving dependencies...
Notice: installing into a sandbox located at
/Users/zaneli/ws/haskell/voicetext/.cabal-sandbox
Downloading OpenAL-1.6.0.1...
Configuring OpenAL-1.6.0.1...
Building OpenAL-1.6.0.1...
Installed OpenAL-1.6.0.1
Downloading ALUT-2.3.0.2...
Configuring ALUT-2.3.0.2...
Failed to install ALUT-2.3.0.2
Last 10 lines of the build log ( /Users/zaneli/ws/haskell/voicetext/.cabal-sandbox/logs/ALUT-2.3.0.2.log ):
Configuring ALUT-2.3.0.2...
setup-Cabal-1.18.1.4-x86_64-osx-ghc-7.8.3: Missing dependency on a foreign
library:
* Missing C library: alut
This problem can usually be solved by installing the system package that
provides this library (you may need the "-dev" version). If the library is
already installed but in a non-standard location then you can use the flags
--extra-include-dirs= and --extra-lib-dirs= to specify where it is.
cabal: Error: some packages failed to install:
ALUT-2.3.0.2 failed during the configure step. The exception was:
ExitFailure 1
むむ、Cのライブラリが無いと言われて失敗した。
まず alut を入れる。Mac の場合、brew で入れられた。
> brew update
> brew install alut
==> Installing freealut
==> Downloading http://connect.creativelabs.com/openal/Downloads/ALUT/freealut-1.1.0.tar.gz
Already downloaded: /Library/Caches/Homebrew/freealut-1.1.0.tar.gz
==> Patching
patching file configure.ac
==> ./autogen.sh
==> ./configure --prefix=/usr/local/Cellar/freealut/1.1.0 --mandir=/usr/local/Cellar/freealut/
==> make install
? /usr/local/Cellar/freealut/1.1.0: 11 files, 180K, built in 27 seconds
もう一度 cabal install --only-dependencies
したが同じエラー。
--extra-lib-dirs オプションで freealutインストールディレクトリを指定したところ、成功した。
> cabal install --only-dependencies --extra-lib-dirs=/usr/local/Cellar/freealut/1.1.0/lib
Resolving dependencies...
Notice: installing into a sandbox located at
/Users/zaneli/ws/haskell/voicetext/.cabal-sandbox
Configuring ALUT-2.3.0.2...
Building ALUT-2.3.0.2...
Installed ALUT-2.3.0.2
実行
音声ファイルを再生
今回の目的からは少し外れているけど、一旦ファイルを作って、それを再生するにはこういう風にする。
import Network.VoiceText
import Sound.ALUT
import System.Environment (getArgs)
main = playVoice
playVoice :: IO ()
playVoice = withProgNameAndArgs runALUTUsingCurrentContext $ \_ _ ->
do
message <- getArgs
let tmpFileName = "./tmp.wav"
ttsToFile tmpFileName (basicAuth "basic_auth_username" "") (ttsParams (head message) Show)
(Just device) <- openDevice Nothing
context <- createContext device []
currentContext $= context
buffer <- createBuffer $ File tmpFileName
[source] <- genObjectNames 1
queueBuffers source [buffer]
play [source]
sleep 1
closeDevice device
return ()
cabal run "Hello, world"
とか実行すると、「はろ〜わ〜るど」とおしゃべりしてくれる。
(毎回tmp.wavファイルが作成されるが…)
音声データを再生
Network.VoiceText には音声ファイルを作成する ttsToFile
関数とは別に、レスポンスデータを ByteString として返す tts
関数を用意しているので、できればファイルを作らず ByteString を直接再生したい。
import Data.ByteString.Internal (toForeignPtr)
import Data.ByteString.Lazy (toStrict)
import Foreign.ForeignPtr (castForeignPtr)
import Foreign.ForeignPtr.Unsafe (unsafeForeignPtrToPtr)
import Network.VoiceText (basicAuth, tts, ttsParams, Speaker(Show))
import Sound.ALUT (
($=),
closeDevice,
createBuffer,
createContext,
currentContext,
genObjectNames,
openDevice,
play,
queueBuffers,
runALUTUsingCurrentContext,
sleep,
SoundDataSource(FileImage),
withProgNameAndArgs)
import System.Environment (getArgs)
import qualified Foreign.C.Types as CT
import qualified Sound.OpenAL.AL.Buffer as AL
main = do
mr <- createVoice
playVoice mr
createVoice :: IO (AL.MemoryRegion a)
createVoice = do
[message] <- getArgs
(Right bytes) <- tts (basicAuth "basic_auth_username" "") (ttsParams message Show)
let (fptrw, _, ptrLength) = toForeignPtr $ toStrict bytes
let fptri = castForeignPtr fptrw
let size = CT.CInt $ fromIntegral ptrLength
let ptr = unsafeForeignPtrToPtr fptri
return $ AL.MemoryRegion ptr size
playVoice :: AL.MemoryRegion a -> IO ()
playVoice mr = withProgNameAndArgs runALUTUsingCurrentContext $ \_ _ -> do
(Just device) <- openDevice Nothing
context <- createContext device []
currentContext $= context
buffer <- createBuffer $ FileImage mr
[source] <- genObjectNames 1
queueBuffers source [buffer]
play [source]
sleep 1
closeDevice device
return ()
後述する参考URLを元に書いてみて、うまく動作することは確認できたが型の変換の辺りよく理解できていない…。
しかしこれで、Haskellとおしゃべりできたぞ!