{-# LANGUAGE NoImplicitPrelude #-} {- | Convert MIDI events of a MIDI controller to a control signal. -} module Synthesizer.Storable.ALSA.Play ( auto, autoAndRecord, monoToInt16, stereoToInt16, defaultChunkSize, ) where import qualified Sound.ALSA.PCM as ALSA import qualified Synthesizer.Frame.Stereo as Stereo import qualified Synthesizer.Basic.Binary as BinSmp import qualified Sound.Sox.Frame as SoxFrame import qualified Sound.Sox.Write as SoxWrite import qualified Sound.Sox.Option.Format as SoxOption import Foreign.Storable (Storable, ) import Foreign.Marshal.Array (advancePtr, ) import Foreign.Ptr (Ptr, minusPtr, ) import qualified System.IO as IO -- import qualified Synthesizer.State.Signal as SigS import qualified Synthesizer.Storable.Signal as SigSt import qualified Data.StorableVector.Lazy as SVL import qualified Data.StorableVector.Base as SVB import qualified Algebra.RealRing as RealRing import NumericPrelude.Numeric import NumericPrelude.Base defaultChunkSize :: SigSt.ChunkSize defaultChunkSize = SigSt.chunkSize 256 makeSink :: (ALSA.SampleFmt y, RealRing.C t) => t -> ALSA.SampleFreq -> ALSA.SoundSink y ALSA.Pcm makeSink periodTime rate = let {-# INLINE soundFormat #-} soundFormat :: ALSA.SoundFmt y soundFormat = ALSA.SoundFmt { ALSA.sampleFreq = rate } in ALSA.alsaSoundSinkTime "plughw:0,0" soundFormat $ ALSA.SoundBufferTime (round (5000000*periodTime)) (round (1000000*periodTime)) {- alsaOpen: only few buffer underruns with let buffer_time = 200000 -- 0.20s period_time = 40000 -- 0.04s However the delay is still perceivable. Latency for keyboard playback might be better with: let buffer_time = 50000 -- 0.05s period_time = 10000 -- 0.01s but we get too much underruns, without actually achieving the required latency. -} {-# INLINE auto #-} auto :: (ALSA.SampleFmt y, RealRing.C t) => t {- ^ period (buffer) size expressed in seconds -} -> ALSA.SampleFreq {- ^ sample rate -} -> SigSt.T y -> IO () auto periodTime rate ys = let sink = makeSink periodTime rate in ALSA.withSoundSink sink $ \to -> flip mapM_ (SVL.chunks ys) $ \c -> SVB.withStartPtr c $ \ptr size -> ALSA.soundSinkWrite sink to ptr size -- cf. Alsa.hs {-# INLINE arraySize #-} arraySize :: Storable y => Ptr y -> Int -> Int arraySize p n = advancePtr p n `minusPtr` p {-# INLINE autoAndRecord #-} autoAndRecord :: (ALSA.SampleFmt y, SoxFrame.C y, RealRing.C t) => t -> FilePath -> ALSA.SampleFreq -> SigSt.T y -> IO () autoAndRecord periodTime fileName rate = let sink = makeSink periodTime rate in (\act -> fmap (const ()) . SoxWrite.simple act SoxOption.none fileName rate) $ \h ys -> ALSA.withSoundSink sink $ \to -> flip mapM_ (SVL.chunks ys) $ \c -> SVB.withStartPtr c $ \ptr size -> ALSA.soundSinkWrite sink to ptr size >> IO.hPutBuf h ptr (arraySize ptr size) {-# INLINE monoToInt16 #-} monoToInt16 :: (Storable y, RealRing.C y, RealRing.C t) => t -> ALSA.SampleFreq -> SigSt.T y -> IO () monoToInt16 periodTime rate xs = auto periodTime rate (SigSt.map BinSmp.int16FromCanonical xs) {-# INLINE stereoToInt16 #-} stereoToInt16 :: (Storable y, RealRing.C y, RealRing.C t) => t -> ALSA.SampleFreq -> SigSt.T (Stereo.T y) -> IO () stereoToInt16 periodTime rate xs = auto periodTime rate (SigSt.map (fmap BinSmp.int16FromCanonical) xs)