conduit-audio 0.1 → 0.2
raw patch · 2 files changed
+59/−47 lines, 2 filesPVP ok
version bump matches the API change (PVP)
API changes (from Hackage documentation)
+ Data.Conduit.Audio: reorganize :: (Monad m, Storable a) => Frames -> AudioSource m a -> AudioSource m a
Files
- conduit-audio.cabal +2/−2
- src/Data/Conduit/Audio.hs +57/−45
conduit-audio.cabal view
@@ -1,5 +1,5 @@ name: conduit-audio-version: 0.1+version: 0.2 author: Michael Tolly maintainer: miketolly@gmail.com license: LGPL@@ -28,7 +28,7 @@ exposed-modules: Data.Conduit.Audio build-depends:- base >= 4.6 && < 4.8+ base >= 4.6 && < 4.9 , conduit , vector hs-source-dirs: src
src/Data/Conduit/Audio.hs view
@@ -21,6 +21,7 @@ , chunkSize , deinterleave, interleave , integralSample, fractionalSample+, reorganize ) where import qualified Data.Vector.Storable as V@@ -29,7 +30,6 @@ 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) -- | An abstraction of a stream of audio. Inside is a Conduit 'C.Source' which@@ -83,7 +83,7 @@ -- | An arbitrary size, in frames, for smallish audio chunks. chunkSize :: Frames-chunkSize = 10000+chunkSize = 5000 -- | Generates a stream of silence with the given parameters. silent :: (Monad m, Num a, V.Storable a) => Duration -> Rate -> Channels -> AudioSource m a@@ -130,7 +130,7 @@ 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)+ let src' = C.mapOutput (\v -> deinterleave c v !! i) src return $ AudioSource src' r 1 l -- | Mixes two audio streams together by adding them samplewise.@@ -140,30 +140,30 @@ -- the result will not be a normal \"clipping\" effect, but will instead overflow, -- producing glitchy audio. 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)+mix a1@(AudioSource _ r1 c1 l1) a2@(AudioSource _ 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 (+)))+ (C.mapOutput (uncurry $ V.zipWith (+)) $ combineAudio a1 a2) r1 c1 (max l1 l2) -- | Combines the channels of two audio streams into a single source with all the channels. -- The two streams must have the same sample rate, but can have any number of channels. 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)+merge a1@(AudioSource _ r1 c1 l1) a2@(AudioSource _ 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))+ (C.mapOutput mergeChunk $ combineAudio a1 a2) r1 (c1 + c2) (max l1 l2)+ where mergeChunk (p1, p2) = interleave $ deinterleave c1 p1 ++ deinterleave c2 p2 -- | Applies a function to every sample in the audio stream. 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+mapSamples f (AudioSource s r c l) = AudioSource (C.mapOutput (V.map f) s) r c l -- | Multiplies all the audio samples by the given scaling factor. -- It is best to use this function on floating-point sample types,@@ -237,9 +237,9 @@ -- converts it into @[[L0, L1, ...], [R0, R1, ...]]@. deinterleave :: (V.Storable a) => Channels -> V.Vector a -> [V.Vector a] deinterleave n v = do- let len = V.length v `div` n+ let fms = vectorFrames v n i <- [0 .. n - 1]- return $ V.generate len $ \j -> v V.! (n * j + i)+ return $ V.generate fms $ \j -> v V.! (n * j + i) -- | Opposite of 'deinterleave'. -- All the input vectors should have the same length.@@ -253,40 +253,34 @@ -- | Combines two audio streams to produce pairs of same-length (in frames) chunks. -- If one stream is shorter, its end will be padded with silence to match the longer one. -- This function is used to implement 'mix' and 'merge'.-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+combineAudio :: (Monad m, V.Storable a, V.Storable b, Num a, Num b)+ => AudioSource m a -> AudioSource m b -> C.Source m (V.Vector a, V.Vector b)+combineAudio src1 src2 = let+ org1 = justify $ source $ reorganize chunkSize src1+ org2 = justify $ source $ reorganize chunkSize src2+ justify src = C.mapOutput Just src >> forever (C.yield Nothing)+ in zipSources org1 org2 =$= let+ loop = C.await >>= \mp -> case mp of+ Nothing -> error "Data.Conduit.Audio.combineAudio: internal error! reached end of infinite stream"+ Just p -> case p of+ (Nothing, Nothing) -> return ()+ (Nothing, Just v2) -> let+ v1 = V.replicate (vectorFrames v2 (channels src2) * channels src1) 0+ in C.yield (v1, v2) >> loop+ (Just v1, Nothing) -> let+ v2 = V.replicate (vectorFrames v1 (channels src1) * channels src2) 0+ in C.yield (v1, v2) >> loop+ (Just v1, Just v2) -> let+ len1 = vectorFrames v1 (channels src1)+ len2 = vectorFrames v2 (channels src2)+ in case compare len1 len2 of+ EQ -> C.yield (v1, v2) >> loop+ LT -> let+ v1' = v1 V.++ V.replicate ((len2 - len1) * channels src1) 0+ in C.yield (v1', v2) >> loop+ GT -> let+ v2' = v2 V.++ V.replicate ((len1 - len2) * channels src2) 0+ in C.yield (v1, v2') >> loop in loop -- See http://blog.bjornroche.com/2009/12/int-float-int-its-jungle-out-there.html@@ -309,3 +303,21 @@ -- samples in the range @[-1, 1]@. fractionalSample :: (Integral a, Bounded a, Fractional b) => a -> b fractionalSample x = fromIntegral x / fromIntegral (maxBound `asTypeOf` x)++reorganizer :: (Monad m, V.Storable a) => Int -> C.Conduit (V.Vector a) m (V.Vector a)+reorganizer samps = let+ go = C.await >>= \ml -> case ml of+ Nothing -> return ()+ Just v -> case compare samps $ V.length v of+ EQ -> C.yield v >> go+ LT -> case V.splitAt samps v of+ (v1, v2) -> C.yield v1 >> C.leftover v2 >> go+ GT -> C.await >>= \m2 -> case m2 of+ Nothing -> C.yield v+ Just v' -> C.leftover (v V.++ v') >> go+ in go++-- | Modifies the source so that it outputs vectors of a consistent length in frames.+-- The last vector from the new source may be less than the given length.+reorganize :: (Monad m, V.Storable a) => Frames -> AudioSource m a -> AudioSource m a+reorganize fms src = src { source = source src =$= reorganizer (fms * channels src) }