module Data.Conduit.Audio
(
AudioSource(..)
, Seconds, Frames, Rate, Channels, Duration(..)
, silent, sine
, concatenate, mix, merge, splitChannels
, padStart, padEnd
, takeStart, takeEnd
, dropStart, dropEnd
, fadeIn, fadeOut
, mapSamples, gain
, vectorFrames
, framesToSeconds, secondsToFrames
, chunkSize
, deinterleave, interleave
, integralSample, fractionalSample
) where
import qualified Data.Vector.Storable as V
import qualified Data.Conduit as C
import Data.Conduit ((=$=))
import qualified Data.Conduit.List as CL
import Data.Conduit.Internal (zipSources)
import Control.Monad (replicateM_, forever, when)
import Data.Maybe (fromMaybe)
import Text.Printf (printf)
data AudioSource m a = AudioSource
{ source :: C.Source m (V.Vector a)
, rate :: Rate
, channels :: Channels
, frames :: Frames
}
type Seconds = Double
type Frames = Int
type Rate = Double
type Channels = Int
data Duration
= Seconds Seconds
| Frames Frames
deriving (Eq, Ord, Show, Read)
vectorFrames :: (V.Storable a) => V.Vector a -> Channels -> Frames
vectorFrames v c = case quotRem (V.length v) c of
(len, 0) -> len
_ -> error $
printf "Data.Conduit.Audio.vectorFrames: block length (%d) not divisible by channel count (%d)"
(V.length v) c
framesToSeconds :: Frames -> Rate -> Seconds
framesToSeconds fms r = fromIntegral fms / r
secondsToFrames :: Seconds -> Rate -> Frames
secondsToFrames secs r = round $ secs * r
chunkSize :: Frames
chunkSize = 10000
silent :: (Monad m, Num a, V.Storable a) => Duration -> Rate -> Channels -> AudioSource m a
silent (Seconds secs) r c = silent (Frames $ secondsToFrames secs r) r c
silent (Frames fms) r c = let
(full, part) = quotRem fms chunkSize
fullChunk = V.replicate (chunkSize * c) 0
partChunk = V.replicate (part * c) 0
src = do
replicateM_ full $ C.yield fullChunk
when (part /= 0) $ C.yield partChunk
in AudioSource src r c fms
sine :: (Monad m, Floating a, V.Storable a) => a -> Duration -> Rate -> AudioSource m a
sine freq (Seconds secs) r = sine freq (Frames $ secondsToFrames secs r) r
sine freq (Frames fms) r = AudioSource (go 0) r 1 fms where
valueAt posn = sin $ 2 * pi * freq * (fromIntegral posn / realToFrac r)
go posn = let
left = fms posn
in if left <= chunkSize
then C.yield $ V.generate left $ \i -> valueAt $ i + posn
else let
firstChunk = V.generate chunkSize $ \i -> valueAt $ i + posn
in C.yield firstChunk >> go (posn + chunkSize)
concatenate :: (Monad m) => AudioSource m a -> AudioSource m a -> AudioSource m a
concatenate (AudioSource s1 r1 c1 l1) (AudioSource s2 r2 c2 l2)
| r1 /= r2 = error $
printf "Data.Conduit.Audio.concatenate: mismatched rates (%d and %d)" r1 r2
| c1 /= c2 = error $
printf "Data.Conduit.Audio.concatenate: mismatched channel counts (%d and %d)" c1 c2
| otherwise = AudioSource (s1 >> s2) r1 c1 (l1 + l2)
padStart, padEnd :: (Monad m, Num a, V.Storable a) => Duration -> AudioSource m a -> AudioSource m a
padStart d src@(AudioSource _ r c _) = concatenate (silent d r c) src
padEnd d src@(AudioSource _ r c _) = concatenate src (silent d r c)
splitChannels :: (Monad m, V.Storable a) => AudioSource m a -> [AudioSource m a]
splitChannels (AudioSource src r c l) = do
i <- [0 .. c 1]
let src' = src =$= CL.map (\v -> deinterleave c v !! i)
return $ AudioSource src' r 1 l
mix :: (Monad m, Num a, V.Storable a) => AudioSource m a -> AudioSource m a -> AudioSource m a
mix (AudioSource s1 r1 c1 l1) (AudioSource s2 r2 c2 l2)
| r1 /= r2 = error $
printf "Data.Conduit.Audio.mix: mismatched rates (%d and %d)" r1 r2
| c1 /= c2 = error $
printf "Data.Conduit.Audio.mix: mismatched channel counts (%d and %d)" c1 c2
| otherwise = AudioSource
(combineAudio c1 c2 s1 s2 =$= CL.map (uncurry $ V.zipWith (+)))
r1 c1 (max l1 l2)
merge :: (Monad m, Num a, V.Storable a) => AudioSource m a -> AudioSource m a -> AudioSource m a
merge (AudioSource s1 r1 c1 l1) (AudioSource s2 r2 c2 l2)
| r1 /= r2 = error $
printf "Data.Conduit.Audio.merge: mismatched rates (%d and %d)" r1 r2
| otherwise = AudioSource
(combineAudio c1 c2 s1 s2 =$= CL.map
(\(p1, p2) -> interleave $ deinterleave c1 p1 ++ deinterleave c2 p2))
r1 (c1 + c2) (max l1 l2)
mapSamples :: (Monad m, V.Storable a, V.Storable b) =>
(a -> b) -> AudioSource m a -> AudioSource m b
mapSamples f (AudioSource s r c l) = AudioSource (s =$= CL.map (V.map f)) r c l
gain :: (Monad m, Num a, V.Storable a) => a -> AudioSource m a -> AudioSource m a
gain d = mapSamples (* d)
fadeIn :: (Monad m, Ord a, Fractional a, V.Storable a) => AudioSource m a -> AudioSource m a
fadeIn (AudioSource s r c l) = let
go i = C.await >>= \mx -> case mx of
Nothing -> return ()
Just v -> let
fader = V.generate (V.length v) $ \j ->
min 1 $ fromIntegral (i + quot j c) / fromIntegral l
in C.yield (V.zipWith (*) v fader) >> go (i + vectorFrames v c)
in AudioSource (s =$= go 0) r c l
fadeOut :: (Monad m, Ord a, Fractional a, V.Storable a) => AudioSource m a -> AudioSource m a
fadeOut (AudioSource s r c l) = let
go i = C.await >>= \mx -> case mx of
Nothing -> return ()
Just v -> let
fader = V.generate (V.length v) $ \j ->
1 (min 1 $ fromIntegral (i + quot j c) / fromIntegral l)
in C.yield (V.zipWith (*) v fader) >> go (i + vectorFrames v c)
in AudioSource (s =$= go 0) r c l
takeStart :: (Monad m, V.Storable a) => Duration -> AudioSource m a -> AudioSource m a
takeStart (Seconds secs) src = takeStart (Frames $ secondsToFrames secs $ rate src) src
takeStart (Frames fms) (AudioSource src r c l) = let
go left = C.await >>= \mx -> case mx of
Nothing -> return ()
Just v -> let
len = V.length v
in case compare left len of
EQ -> C.yield v
LT -> C.yield $ V.take left v
GT -> C.yield v >> go (left len)
in AudioSource (src =$= go (fms * c)) r c (min l fms)
dropStart :: (Monad m, V.Storable a) => Duration -> AudioSource m a -> AudioSource m a
dropStart (Seconds secs) src = dropStart (Frames $ secondsToFrames secs $ rate src) src
dropStart (Frames fms) (AudioSource src r c l) = let
go left = C.await >>= \mx -> case mx of
Nothing -> return ()
Just v -> let
len = V.length v
in case compare left len of
EQ -> CL.map id
LT -> C.yield (V.drop left v) >> CL.map id
GT -> go (left len)
in AudioSource (src =$= go (fms * c)) r c (max 0 $ l fms)
takeEnd, dropEnd :: (Monad m, V.Storable a) => Duration -> AudioSource m a -> AudioSource m a
takeEnd (Frames fms) src = dropStart (Frames $ frames src fms) src
takeEnd (Seconds secs) src = takeEnd (Frames $ secondsToFrames secs $ rate src) src
dropEnd (Frames fms) src = takeStart (Frames $ frames src fms) src
dropEnd (Seconds secs) src = dropEnd (Frames $ secondsToFrames secs $ rate src) src
deinterleave :: (V.Storable a) => Channels -> V.Vector a -> [V.Vector a]
deinterleave n v = do
let len = V.length v `div` n
i <- [0 .. n 1]
return $ V.generate len $ \j -> v V.! (n * j + i)
interleave :: (V.Storable a) => [V.Vector a] -> V.Vector a
interleave vs = let
n = length vs
in V.generate (sum $ map V.length vs) $ \i -> let
(q, r) = quotRem i n
in (vs !! r) V.! q
combineAudio
:: (Num a, V.Storable a, Monad m)
=> Int
-> Int
-> C.Source m (V.Vector a)
-> C.Source m (V.Vector a)
-> C.Source m (V.Vector a, V.Vector a)
combineAudio c1 c2 s1 s2 = let
justify src = (src =$= CL.map Just) >> forever (C.yield Nothing)
await' = C.await >>= \mx -> case mx of
Nothing -> error
"Data.Conduit.Audio.combineAudio: internal error! reached end of infinite stream"
Just x -> return x
in zipSources (justify s1) (justify s2) =$= let
loop = await' >>= \pair -> case pair of
(Nothing, Nothing) -> return ()
(Just v1, Nothing) -> let
v2 = V.replicate (vectorFrames v1 c1 * c2) 0
in C.yield (v1, v2) >> loop
(Nothing, Just v2) -> let
v1 = V.replicate (vectorFrames v2 c2 * c1) 0
in C.yield (v1, v2) >> loop
(Just v1, Just v2) -> case compare (vectorFrames v1 c1) (vectorFrames v2 c2) of
EQ -> C.yield (v1, v2) >> loop
LT -> let
(v2a, v2b) = V.splitAt (vectorFrames v1 c1 * c2) v2
in C.yield (v1, v2a) >> await' >>= \(next1, next2) -> do
C.leftover (next1, Just $ v2b V.++ fromMaybe V.empty next2)
loop
GT -> let
(v1a, v1b) = V.splitAt (vectorFrames v2 c2 * c1) v1
in C.yield (v1a, v2) >> await' >>= \(next1, next2) -> do
C.leftover (Just $ v1b V.++ fromMaybe V.empty next1, next2)
loop
in loop
integralSample :: (RealFrac a, Integral b, Bounded b) => a -> b
integralSample x
| x <= (1) = minBound
| x >= 1 = maxBound
| otherwise = let
result = round $ x * fromIntegral (maxBound `asTypeOf` result)
in result
fractionalSample :: (Integral a, Bounded a, Fractional b) => a -> b
fractionalSample x = fromIntegral x / fromIntegral (maxBound `asTypeOf` x)