2013-02-02 11 views
11

私はportaudioを使っていくつかのオーディオを処理しています。 haskell FFIバインディングは、処理されるオーディオデータがあるときはいつでも、ユーザー定義のコールバックを呼び出します。このコールバックは非常に素早く理想的にはI/Oなしで処理する必要があります。アプリケーションをリアルタイムでオーディオに反応させる必要がないため、すぐにオーディオ入力を保存して戻りたいと思っていました(今はオーディオデータをファイルに保存していますが、後で簡単な音声認識システムを構築します) 。ハスケルのパイプとコールバック

私はpipesのアイデアが好きで、私はそのライブラリを使うことができると考えました。問題は、コールバックを介して入ったデータを返すProducerを作成する方法がわからないことです。

どのように私のユースケースを処理しますか?


ここで私は...(データムMVARが今働いていないが、私は、配列内のすべてのデータを格納好きではないのに役立ちます場合には、私は今で働いている何だろうそれが終わりに来ただけの代わりとしてではなく)、それを処理する:

{-# LANGUAGE FlexibleInstances, MultiParamTypeClasses #-} 

module Main where 

import Codec.Wav 

import Sound.PortAudio 
import Sound.PortAudio.Base 
import Sound.PortAudio.Buffer 

import Foreign.Ptr 
import Foreign.ForeignPtr 
import Foreign.C.Types 
import Foreign.Storable 

import qualified Data.StorableVector as SV 
import qualified Data.StorableVector.Base as SVB 

import Control.Exception.Base (evaluate) 

import Data.Int 
import Data.Sequence as Seq 

import Control.Concurrent 

instance Buffer SV.Vector a where 
    fromForeignPtr fp = return . SVB.fromForeignPtr fp 
    toForeignPtr = return . (\(a, b, c) -> (a, c)) . SVB.toForeignPtr 

-- | Wrap a buffer callback into the generic stream callback type. 
buffCBtoRawCB' :: (StreamFormat input, StreamFormat output, Buffer a input, Buffer b output) => 
    BuffStreamCallback input output a b -> StreamCallback input output  
buffCBtoRawCB' func = \a b c d e -> do 
    fpA <- newForeignPtr_ d -- We will not free, as callback system will do that for us 
    fpB <- newForeignPtr_ e -- We will not free, as callback system will do that for us 
    storeInp <- fromForeignPtr fpA (fromIntegral $ 1 * c) 
    storeOut <- fromForeignPtr fpB (fromIntegral $ 0 * c) 
    func a b c storeInp storeOut 

callback :: MVar (Seq.Seq [Int32]) -> PaStreamCallbackTimeInfo -> [StreamCallbackFlag] -> CULong 
      -> SV.Vector Int32 -> SV.Vector Int32 -> IO StreamResult 
callback seqmvar = \timeinfo flags numsamples input output -> do 
    putStrLn $ "timeinfo: " ++ show timeinfo ++ "; flags are " ++ show flags ++ " in callback with " ++ show numsamples ++ " samples." 
    print input 
    -- write data to output 
    --mapM_ (uncurry $ pokeElemOff output) $ zip (map fromIntegral [0..(numsamples-1)]) datum 
    --print "wrote data" 

    input' <- evaluate $ SV.unpack input 
    modifyMVar_ seqmvar (\s -> return $ s Seq.|> input') 

    case flags of 
    [] -> return $ if unPaTime (outputBufferDacTime timeinfo) > 0.2 then Complete else Continue 
    _ -> return Complete 

done doneMVar = do 
    putStrLn "total done dood!" 
    putMVar doneMVar True 
    return() 

main = do 

    let samplerate = 16000 

    Nothing <- initialize 

    print "initialized" 

    m <- newEmptyMVar 
    datum <- newMVar Seq.empty 

    Right s <- openDefaultStream 1 0 samplerate Nothing (Just $ buffCBtoRawCB' (callback datum)) (Just $ done m) 
    startStream s 

    _ <- takeMVar m -- wait until our callbacks decide they are done! 
    Nothing <- terminate 

    print "let's see what we've recorded..." 

    stuff <- takeMVar datum 
    print stuff 

    -- write out wav file 

    -- let datum = 
    --  audio = Audio { sampleRate = samplerate 
    --     , channelNumber = 1 
    --     , sampleData = datum 
    --     } 
    -- exportFile "foo.wav" audio 

    print "main done" 
+0

パイプなしでコールバックからデータを取得するコードの例をいくつか挙げることができますか? – Davorak

+1

おそらく 'MVar'よりもむしろチャンネルを使うことを考えてみましょう。これらは生産者や消費者の問題のために非常にうまく機能します。 – sabauma

答えて

13

最も簡単な解決策は、コールバックとProducerとの間で通信するためにMVar Sを使用することです。

import Control.Proxy 
import Control.Concurrent.MVar 

fromMVar :: (Proxy p) => MVar (Maybe a) ->() -> Producer p a IO() 
fromMVar mvar() = runIdentityP loop where 
    loop = do 
     ma <- lift $ takeMVar mvar 
     case ma of 
      Nothing -> return() 
      Just a -> do 
       respond a 
       loop 

あなたのストリームのコールバックがMVarからJust inputを書きますし、あなたのファイナライズのコールバックがProducerを終了するNothingを書き込みます:ここでは方法です。

はここにそれがどのように機能するかを実証ghci例です:

>>> mvar <- newEmptyMVar :: IO (MVar (Maybe Int)) 
>>> forkIO $ runProxy $ fromMVar mvar >-> printD 
>>> putMVar mvar (Just 1) 
1 
>>> putMVar mvar (Just 2) 
2 
>>> putMVar mvar Nothing 
>>> putMVar mvar (Just 3) 
>>> 

は編集:The pipes-concurrency library今、この機能を提供し、それもコールバックからデータを取得するためにそれを使用する方法を具体的に説明するとsection in the tutorialを持っています。

+5

消費者が最新の値をまだ取っていないときに 'putMVar'をブロックするのではなく、実行をインタリーブしたい場合は、' Chan'を簡単に使うことができます。コストは、より多くのメモリを使用することです。 –

+2

そうです。コールバックをプロデューサと同期させるかどうかによって異なります。 –