synthesizer-llvm-0.9: alsa/Synthesizer/LLVM/Server/ALSA.hs
module Synthesizer.LLVM.Server.ALSA (
Output,
play, playChunk,
record,
put,
startMessage,
makeNote,
) where
import qualified Synthesizer.LLVM.Server.Option as Option
import qualified Synthesizer.ALSA.CausalIO.Process as PIO
import qualified Sound.ALSA.Sequencer.Event as Event
import qualified Sound.ALSA.Sequencer.Address as Addr
import qualified Sound.ALSA.Sequencer.Time as Time
import qualified Sound.ALSA.Sequencer.RealTime as RealTime
import qualified Sound.ALSA.PCM.Node.ALSA as PCM
import qualified Sound.ALSA.PCM.Parameters.Software as SwParam
import qualified Sound.ALSA.PCM.Parameters.Hardware as HwParam
import qualified Synthesizer.Storable.Signal as SigSt
import qualified Data.StorableVector.Lazy as SVL
import qualified Data.StorableVector.Base as SVB
import qualified Algebra.Additive as Additive
import Control.Functor.HT (void)
import qualified System.IO as IO
import Prelude hiding (Real, round)
getOptParams :: Option.T -> h -> ((PCM.Size, PCM.SampleFreq), h)
getOptParams opt h =
((case Option.chunkSize opt of SVL.ChunkSize size -> size,
case Option.sampleRate opt of
Nothing -> 44100
Just (Option.SampleRate rate) -> rate),
h)
type Output handle signal a = Option.T -> PIO.Output handle signal a
record ::
(PCM.SampleFmt y) =>
FilePath -> Output IO.Handle (SigSt.T y) ()
record name opt =
(fmap (getOptParams opt) $ IO.openFile name IO.WriteMode,
IO.hClose,
SVL.hPut)
put :: (Show signal) => Output () signal ()
put opt =
(return $ getOptParams opt (),
return,
\() -> print)
playChunk ::
(Additive.C y, PCM.SampleFmt y) =>
Output (PCM.Handle HwParam.Interleaved y) (SVB.Vector y) ()
playChunk opt =
(openPCM opt, closePCM, write)
-- ToDo: do not record the empty chunk that is inserted for latency
{-# INLINE play #-}
play ::
(Additive.C y, PCM.SampleFmt y) =>
Output (PCM.Handle HwParam.Interleaved y) (SigSt.T y) ()
play opt =
(openPCM opt, closePCM, \h -> mapM_ (write h) . SVL.chunks)
{-
Play.auto (Play.makeSink
(Option.device opts) (Option.periodTime opts) (round rate)) .
SigSt.append (SigSt.replicate (Option.chunkSize opts) (Option.latency opts) zero)
-- FiltG.delayPosLazySize (Option.chunkSize opts) (Option.latency opts)
-- FiltG.delayPos (Option.latency opts)
-}
putLog :: String -> IO ()
putLog = putStrLn
openPCM ::
(PCM.Access i, PCM.SampleFmt y) =>
Option.T ->
IO ((PCM.Size, PCM.SampleFreq), PCM.Handle i y)
openPCM opt = do
putLog "alsaOpen"
(((bufferSize,periodSize),(bufferTime,periodTime),sampleRate), h) <-
PCM.open (PCM.modes []) PCM.StreamPlayback
(setHwParams (Option.sampleRate opt) (Option.chunkSize opt))
(\q@(sizes,_,_) -> do
uncurry SwParam.setBufferSize sizes
return q)
(Option.device opt)
PCM.prepare h
putLog $ "bufferTime = " ++ show bufferTime
putLog $ "bufferSize = " ++ show bufferSize
putLog $ "periodTime = " ++ show periodTime
putLog $ "periodSize = " ++ show periodSize
return ((periodSize, sampleRate), h)
closePCM :: PCM.Handle i y -> IO ()
closePCM pcm = do
putLog "alsaClose"
PCM.drain pcm
PCM.close pcm
setHwParams ::
Maybe (Option.SampleRate Int) ->
SVL.ChunkSize ->
HwParam.T i y ((PCM.Size,PCM.Size),(PCM.Time,PCM.Time),PCM.SampleFreq)
-- ^ ((bufferSize,periodSize),(bufferTime,periodTime),sampleRate)
setHwParams mrate (SVL.ChunkSize periodSize) = do
(actualRate,_) <-
case mrate of
Nothing -> do
HwParam.setRateResample False
HwParam.setRateNear 44100 EQ
Just (Option.SampleRate rate) -> do
HwParam.setRateResample True
HwParam.setRateNear rate EQ
(actualPeriodSize,_) <-
HwParam.setPeriodSizeNear periodSize EQ
actualBufferSize <-
HwParam.setBufferSizeNear
(max periodSize (actualPeriodSize*4))
(actualBufferTime,_) <- HwParam.getBufferTime
(actualPeriodTime,_) <- HwParam.getPeriodTime
return ((actualBufferSize, actualPeriodSize),
(actualBufferTime, actualPeriodTime),
actualRate)
write ::
(PCM.SampleFmt y) =>
PCM.Handle PCM.Interleaved y -> SVB.Vector y -> IO ()
write h xs =
SVB.withStartPtr xs $ \buf ->
void . PCM.writeiRetry h buf . fromIntegral
startMessage :: String
startMessage =
"run 'aconnect' to connect to the MIDI controller"
-- cf. synthesizer-alsa:Synthesizer.ALSA.Storable.Server.Test
makeNote :: Event.NoteEv -> Int -> Event.T
makeNote typ pit =
(Event.simple Addr.subscribers $ Event.NoteEv typ $
Event.simpleNote (Event.Channel 0)
(Event.Pitch $ fromIntegral pit) Event.normalVelocity)
{ Event.time =
Time.consAbs $ Time.Real $ RealTime.fromInteger 0
}