packages feed

vocoder-dunai-0.1.0.0: example/VocoderJack.hs

{-# LANGUAGE TypeFamilies, RankNTypes, PatternSynonyms, ViewPatterns #-}
module Main where

import qualified Sound.JACK as JACK
import Sound.JACK.Exception
import qualified Sound.JACK.Audio as Audio
import qualified Data.Vector.Storable as V
import Data.Array.Storable as A
import Data.List.Split
import Text.Read hiding (lift)
import Control.Monad.Trans.Class(lift)
import Control.Monad.Trans.Reader(ReaderT(..))
import Control.Monad.Exception.Synchronous(ExceptionalT)
import Control.Concurrent.MVar
import Control.Concurrent
import Control.Monad
import Control.Arrow
import Options.Applicative
import Vocoder
import Vocoder.Filter
import Vocoder.Window
import Vocoder.Dunai
import FRP.Rhine
import MVarClock
import ProcessingTree

type AudioV = V.Vector Audio.Sample

type EventIO = EventMVarT [AudioV] IO

type MyClock = HoistClock EventIO IO (MVarClock [AudioV])

type MyMonad = ReaderT (TimeInfo MyClock) IO

data WindowType = BoxWindow | HammingWindow | HannWindow | BlackmanWindow | FlatTopWindow deriving (Read, Show)

data Cmd = SourceCmd Int | MSFCmd (MSF MyMonad [STFTFrame] [STFTFrame]) | FilterCmd (Filter MyMonad) | NamedCmd String | BindCmd String | BinaryCmd (STFTFrame -> STFTFrame -> STFTFrame)

data Options = Options {
    optClientName :: String,
    optMaybeFrameSize :: Maybe Length,
    optWindowSize :: Length,
    optHopSize :: HopSize,
    optWindowType :: WindowType,
    optProcessingTree :: ProcessingTree MyMonad
}

optFrameSize :: Options -> Length
optFrameSize opts = maybe (optWindowSize opts) id $ optMaybeFrameSize opts

optWindow :: Options -> Window
optWindow opts = windowFun (optWindowType opts) (optWindowSize opts)

optSources :: Options -> Int
optSources opts = numSourcesPT $ optProcessingTree opts

windowFun :: WindowType -> Length -> Window
windowFun BoxWindow = boxWindow
windowFun HammingWindow = hammingWindow
windowFun HannWindow = hannWindow
windowFun BlackmanWindow = blackmanWindow
windowFun FlatTopWindow = flatTopWindow

paramsFor :: Options -> VocoderParams
paramsFor opts = vocoderParams (optFrameSize opts) (optHopSize opts) (optWindow opts)

auto2 :: (Read a, Read b) => ReadM (a, b)
auto2 = maybeReader $ f . splitOn ","
    where
    f [a,b] = (,) <$> readMaybe a  <*> readMaybe b
    f _ = Nothing

auto3 :: (Read a, Read b, Read c) => ReadM (a, b, c)
auto3 = maybeReader $ f . splitOn ","
    where
    f [a,b,c] = (,,) <$> readMaybe a  <*> readMaybe b <*> readMaybe c
    f _ = Nothing

uncurry3 :: (a -> b -> c -> d) -> ((a, b, c) -> d)
uncurry3 f (a,b,c) = f a b c

processingP :: Parser (ProcessingTree MyMonad)
processingP = parseCommands <$> many commandP

ptht :: [ProcessingTree MyMonad] -> [ProcessingTree MyMonad]
ptht (h:t) = h:t
ptht [] = [PTSource 0]

infixr :?

pattern (:?) :: ProcessingTree MyMonad
             -> [ProcessingTree MyMonad] 
             -> [ProcessingTree MyMonad]
pattern h :? t <- (ptht -> h:t)

parseCommands :: [Cmd] -> ProcessingTree MyMonad
parseCommands cmds = p [] cmds 
    where
    p (h :? _) [] = h
    p s             (SourceCmd k : t) = p (PTSource k : s) t
    p s             (NamedCmd n  : t) = p (PTNamed n : s) t
    p (h :? s)      (MSFCmd f    : t) = p (PTMSF f h : s) t
    p (h :? s)      (FilterCmd f : t) = p (PTFilter f h : s) t
    p (h :? i :? s) (BinaryCmd f : t) = p (PTBinary f i h : s) t
    p (h :? s)      (BindCmd n   : t) = p (PTBind n h : s) t

mkBinary :: (Double -> Double -> Double)
         -> (Double -> Double -> Double)
         -> STFTFrame -> STFTFrame -> STFTFrame
mkBinary op1 op2 (mag1, ph_inc1) (mag2, ph_inc2) = (V.zipWith op1 mag1 mag2, V.zipWith op2 ph_inc1 ph_inc2)

commandP :: Parser Cmd
commandP = FilterCmd <$> filterP
       <|> MSFCmd <$> msfP
       <|> (SourceCmd <$> option auto
             ( long "source"
            <> metavar "NUM"
            <> help "Source from JACK input"))
       <|> (NamedCmd <$> option auto
             ( long "named"
            <> metavar "NAME"
            <> help "Source from named stream"))
       <|> (BindCmd <$> option auto
             ( long "bind"
            <> metavar "NAME"
            <> help "Bind stream to name"))
       <|> (flag' (BinaryCmd $ mkBinary (*) (+))
             ( long "multiply"
            <> help "Multiply two streams"))
       <|> (flag' (BinaryCmd $ mkBinary (/) (-))
             ( long "divide"
            <> help "Divide two streams"))
       <|> (flag' (BinaryCmd addFrames)
             ( long "add"
            <> help "Add two streams"))

delayMSF :: Int -> MSF MyMonad [STFTFrame] [STFTFrame]
delayMSF k = mealy f []
    where 
    f i s = (take (length i) s', drop (length s' - k) s') where s' = s ++ i

msfP :: Parser (MSF MyMonad [STFTFrame] [STFTFrame])
msfP = (delayMSF <$> option auto
             ( long "delay"
            <> metavar "HOPS"
            <> help "Delay the signal by some number of STFT hops"))

filterP :: Parser (Filter MyMonad)
filterP = (lowpassBrickwall <$> option auto
             ( long "lowpassBrickwall"
            <> metavar "FREQ"
            <> help "Low-pass brickwall filter"))
      <|> (highpassBrickwall <$> option auto
             ( long "highpassBrickwall"
            <> metavar "FREQ"
            <> help "High-pass brickwall filter"))
      <|> (uncurry bandpassBrickwall <$> option auto2
             ( long "bandpassBrickwall"
            <> metavar "FREQ,FREQ"
            <> help "Band-pass brickwall filter"))
      <|> (uncurry bandstopBrickwall <$> option auto2
             ( long "bandstopBrickwall"
            <> metavar "FREQ,FREQ"
            <> help "Band-stop brickwall filter"))
      <|> (uncurry lowpassButterworth <$> option auto2
             ( long "lowpassButterworth"
            <> metavar "DEG,FREQ"
            <> help "Low-pass Butterworth-style filter"))
      <|> (uncurry highpassButterworth <$> option auto2
             ( long "highpassButterworth"
            <> metavar "DEG,FREQ"
            <> help "High-pass Butterworth-style filter"))
      <|> (uncurry3 bandpassButterworth <$> option auto3
             ( long "bandpassButterworth"
            <> metavar "DEG,FREQ,FREQ"
            <> help "Band-pass Butterworth-style filter"))
      <|> (uncurry3 bandstopButterworth <$> option auto3
             ( long "bandstopButterworth"
            <> metavar "DEG,FREQ,FREQ"
            <> help "Band-stop Butterworth-style filter"))
      <|> (amplify <$> option auto
             ( long "amplify"
            <> metavar "COEFF"
            <> help "Change amplitude"))
      <|> (pitchShiftInterpolate <$> option auto
             ( long "pitchShiftInterpolate"
            <> metavar "COEFF"
            <> help "Interpolative pitch-shift"))
      <|> (envelopeFilter <$> option auto
             ( long "envelope"
            <> metavar "KSIZE"
            <> help "Calculate spectral envelope"))
      <|> (flag' (randomPhaseFilter)
             ( long "randomPhase"
            <> help "Randomize phases (Paulstretch effect)"))

options :: Parser Options
options = Options
    <$> option auto
        ( long "clientName"
       <> metavar "NAME"
       <> value "vocoder-jack"
       <> help "JACK client name")
    <*> optional (option auto
        ( long "frameSize"
       <> metavar "SIZE"
       <> help "Size of zero-padded FFT frame, must be >= windowSize"))
    <*> option auto
        ( long "windowSize"
       <> metavar "SIZE"
       <> value 1024
       <> showDefault
       <> help "Size of STFT window, must be divisible by hopSize")
    <*> option auto
        ( long "hopSize"
       <> metavar "SIZE"
       <> value 128
       <> showDefault
       <> help "STFT hop size, must be a power of 2")
    <*> option auto
        ( long "windowType"
       <> metavar "TYPE"
       <> value BlackmanWindow
       <> showDefault
       <> help "Type of STFT window")
    <*> processingP

runFilter :: JACK.Client -> Options -> ClSF IO MyClock [[STFTFrame]] [STFTFrame]
runFilter client opts = ret
    where
    freqStep = do
        rate <- liftIO $ JACK.getSampleRate client
        return $ fromIntegral rate / fromIntegral (optFrameSize opts)
    (Just ret) = elaboratePT freqStep (optProcessingTree opts)

processing :: JACK.Client -> Options -> MVar AudioV -> ClSF IO MyClock () ()
processing client opts omvar = 
            tagS 
        >>> arr (map $ V.map realToFrac) 
        >>> ((analysisSrcs srcs >>> runFilter client opts >>> synthesis par (zeroPhase par)) &&& arr (V.length . head))
        >>> sumFramesWithLengthS (vocHopSize par) >>> volumeFix par
        >>> arr (V.map realToFrac) 
        >>> arrMCl (liftIO . fmap (const ()) . tryPutMVar omvar)
    where
    par = paramsFor opts
    srcs = optSources opts
    analysisSrcs 0 = pure []
    analysisSrcs k = (:) <$> (arr (!! (srcs-k)) >>> framesOfS (vocInputFrameLength par) (vocHopSize par) >>> analysis par (zeroPhase par)) <*> analysisSrcs (k-1)

main :: IO ()
main = execParser opts >>= run
    where
    opts = info (options <**> helper)
            ( fullDesc
           <> progDesc "Process JACK stream"
           <> header "Phase vocoder audio processing")

withInputPorts :: (ThrowsPortRegister e, ThrowsErrno e)
               => JACK.Client
               -> Options
               -> ([Audio.Port JACK.Input] -> ExceptionalT e IO a)
               -> ExceptionalT e IO a
withInputPorts client opts cont = f (optSources opts) [] where
    f 0 l = cont l
    f k l = JACK.withPort client ("input" ++ show k) $ \iport -> f (k-1) (iport:l)

run :: Options -> IO ()
run opts = do
    imvar <- newEmptyMVar
    omvar <- newEmptyMVar 
    JACK.handleExceptions $ 
        JACK.withClientDefault (optClientName opts) $ \client -> 
        withInputPorts client opts $ \iports ->
        JACK.withPort client "output" $ \oport ->
        JACK.withProcess client (lift . processJack imvar omvar iports oport) $
        JACK.withActivation client $ do
            _ <- lift $ forkIO $ flow $ processing client opts omvar @@ mVarClockOn imvar
            lift $ JACK.waitForBreak
    
processJack :: MVar [AudioV] -> MVar AudioV -> [Audio.Port JACK.Input] -> Audio.Port JACK.Output -> JACK.NFrames -> IO ()
processJack imvar omvar iports oport nframes@(JACK.NFrames frames) = do
    iArrs <- forM iports $ \iport -> Audio.getBufferArray iport nframes
    oArr <- Audio.getBufferArray oport nframes
    iVecs <- forM iArrs $ \iArr -> V.generateM (fromIntegral frames) $ \i -> fmap realToFrac $ A.readArray iArr $ JACK.NFrames $ fromIntegral i
    _ <- tryPutMVar imvar iVecs
    moVec <- tryTakeMVar omvar
    case moVec of
        Just oVec ->
            forM_ (JACK.nframesIndices nframes) $ \ni@(JACK.NFrames i) ->
                writeArray oArr ni $ realToFrac $ oVec V.! fromIntegral i
        Nothing ->
            forM_ (JACK.nframesIndices nframes) $ \ni ->
                writeArray oArr ni 0