csound-sampler 0.0.2 → 0.0.3
raw patch · 4 files changed
+363/−75 lines, 4 files
Files
- csound-sampler.cabal +4/−1
- src/Csound/Sam.hs +6/−74
- src/Csound/Sam/Core.hs +87/−0
- src/Csound/Sam/Ui.hs +266/−0
csound-sampler.cabal view
@@ -1,5 +1,5 @@ name: csound-sampler-version: 0.0.2+version: 0.0.3 license: BSD3 license-file: LICENSE author: Anton Kholomiov@@ -28,5 +28,8 @@ build-depends: base >= 4, base < 5, transformers >= 0.3, csound-expression >= 4.1.0 exposed-modules: Csound.Sam+ Csound.Sam.Core+ Csound.Sam.Ui Csound.Sam.Chord+
src/Csound/Sam.hs view
@@ -21,7 +21,10 @@ arpUp, arpDown, arpOneOf, arpFreqOf, arpUp1, arpDown1, arpOneOf1, arpFreqOf1, -- * Misc patterns- wall, forAirports, genForAirports+ wall, forAirports, genForAirports,++ -- UIs+ module Csound.Sam.Ui ) where import Control.Monad.Trans.Class@@ -29,76 +32,8 @@ import Control.Monad.Trans.Reader import Csound.Base---- | The main type. A stereo sample.-type Sam = Sample Sig2--data Dur = Dur D | InfDur---- | The Beats Per Minute measure (BPM). Almost all values are measured in BPMs.-type Bpm = D---- | The generic type for samples.-newtype Sample a = Sam { unSam :: ReaderT Bpm SE (S a) - } deriving (Functor)--instance Applicative Sample where- pure = Sam . pure . pure- (Sam rf) <*> (Sam ra) = Sam $ liftA2 (<*>) rf ra--data S a = S- { samSig :: a- , samDur :: Dur - } deriving (Functor)--instance Applicative S where- pure a = S a InfDur- (S f df) <*> (S a da) = S (f a) $ case (df, da) of- (Dur durF, Dur durA) -> Dur $ maxB durF durA- _ -> InfDur--instance Num a => Num (Sample a) where- (+) = liftA2 (+)- (*) = liftA2 (*)- (-) = liftA2 (-)- negate = fmap negate- abs = fmap abs- signum = fmap signum- fromInteger = pure . fromInteger--instance Fractional a => Fractional (Sample a) where- recip = fmap recip- fromRational = pure . fromRational--instance SigSpace a => SigSpace (Sample a) where- mapSig f = fmap (mapSig f)--instance RenderCsd Sam where- renderCsdBy opt sample = renderCsdBy opt (runSam (120 * 4) sample)---- | Hides the effects inside sample.-liftSam :: Sample (SE a) -> Sample a-liftSam (Sam ra) = Sam $ do- a <- ra- lift $ fmap (\x -> a{ samSig = x}) $ samSig a---- | Transforms the sample with BPM.-mapBpm :: (Bpm -> Sig2 -> Sig2) -> Sam -> Sam-mapBpm f (Sam ra) = Sam $ do- bpm <- ask- a <- ra- return $ a { samSig = f bpm $ samSig a }---- | Lifts bind on stereo signals to samples.-bindSam :: (Sig2 -> SE Sig2) -> Sam -> Sam-bindSam f = liftSam . fmap f---- | Lifts bind on stereo signals to samples with BPM.-bindBpm :: (Bpm -> Sig2 -> SE Sig2) -> Sam -> Sam-bindBpm f (Sam ra) = Sam $ do- bpm <- ask- a <- ra- lift $ fmap (\x -> a{ samSig = x}) $ f bpm $ samSig a+import Csound.Sam.Core+import Csound.Sam.Ui -- | Constructs sample from mono signal infSig1 :: Sig -> Sam@@ -236,9 +171,6 @@ toSecSig :: Bpm -> Sig -> Sig toSecSig bpm a = a * 60 / sig bpm--runSam :: Bpm -> Sam -> SE Sig2-runSam bpm x = fmap samSig $ runReaderT (unSam x) bpm addDur :: D -> Dur -> Dur addDur d x = case x of
+ src/Csound/Sam/Core.hs view
@@ -0,0 +1,87 @@+-- | The core types/ They are not imported by default.+{-# Language DeriveFunctor, TypeSynonymInstances, FlexibleInstances #-}+module Csound.Sam.Core (+ Sam, runSam, Sample(..), S(..), Dur(..), Bpm,+ liftSam, mapBpm, bindSam, bindBpm+) where++import Control.Applicative+import Control.Monad.Trans.Reader+import Control.Monad.Trans.Class++import Csound.Base++-- | The main type. A stereo sample.+type Sam = Sample Sig2++instance RenderCsd Sam where+ renderCsdBy opt sample = renderCsdBy opt (runSam (120 * 4) sample)++runSam :: Bpm -> Sam -> SE Sig2+runSam bpm x = fmap samSig $ runReaderT (unSam x) bpm++data Dur = Dur D | InfDur++-- | The Beats Per Minute measure (BPM). Almost all values are measured in BPMs.+type Bpm = D++-- | The generic type for samples.+newtype Sample a = Sam { unSam :: ReaderT Bpm SE (S a) + } deriving (Functor)++instance Applicative Sample where+ pure = Sam . pure . pure+ (Sam rf) <*> (Sam ra) = Sam $ liftA2 (<*>) rf ra++data S a = S+ { samSig :: a+ , samDur :: Dur + } deriving (Functor)++instance Applicative S where+ pure a = S a InfDur+ (S f df) <*> (S a da) = S (f a) $ case (df, da) of+ (Dur durF, Dur durA) -> Dur $ maxB durF durA+ _ -> InfDur++instance Num a => Num (Sample a) where+ (+) = liftA2 (+)+ (*) = liftA2 (*)+ (-) = liftA2 (-)+ negate = fmap negate+ abs = fmap abs+ signum = fmap signum+ fromInteger = pure . fromInteger++instance Fractional a => Fractional (Sample a) where+ recip = fmap recip+ fromRational = pure . fromRational++instance SigSpace a => SigSpace (Sample a) where+ mapSig f = fmap (mapSig f)++-- Lifters++-- | Hides the effects inside sample.+liftSam :: Sample (SE a) -> Sample a+liftSam (Sam ra) = Sam $ do+ a <- ra+ lift $ fmap (\x -> a{ samSig = x}) $ samSig a++-- | Transforms the sample with BPM.+mapBpm :: (Bpm -> Sig2 -> Sig2) -> Sam -> Sam+mapBpm f (Sam ra) = Sam $ do+ bpm <- ask+ a <- ra+ return $ a { samSig = f bpm $ samSig a }++-- | Lifts bind on stereo signals to samples.+bindSam :: (Sig2 -> SE Sig2) -> Sam -> Sam+bindSam f = liftSam . fmap f++-- | Lifts bind on stereo signals to samples with BPM.+bindBpm :: (Bpm -> Sig2 -> SE Sig2) -> Sam -> Sam+bindBpm f (Sam ra) = Sam $ do+ bpm <- ask+ a <- ra+ lift $ fmap (\x -> a{ samSig = x}) $ f bpm $ samSig a
+ src/Csound/Sam/Ui.hs view
@@ -0,0 +1,266 @@+-- | Graphical widgets for playing samples+module Csound.Sam.Ui(+ freeSim, hfreeSim, freeSimWith, hfreeSimWith,+ freeTog, hfreeTog, + sim, hsim, simWith, hsimWith,+ tog, htog,+ live, liveEf, + mixSam, uiSam, addGain+) where++import Data.List(transpose)+import Control.Monad+import Control.Monad.Trans.Reader++import Csound.Base+import Csound.Sam.Core++groupToggles :: ([Sig2] -> Sig2) -> [Sam] -> [Evt D] -> Sam+groupToggles group sams ts = Sam $ reader $ \r -> + S (group $ zipWith (\sam t -> schedToggle (runSam r sam) t) sams ts) InfDur ++-- | A widget for playing several samples at the same time (aka `sim`ultaneously).+-- The prefix `free` means no syncronization. the samples start to play when the button is pressed.+freeSim :: [(String, Sam)] -> Source Sam+freeSim = genFreeSim ver++-- | It's just like the function @freeSim@ but the visual representation is horizontal.+-- That's why there is a prefix @h@.+hfreeSim :: [(String, Sam)] -> Source Sam+hfreeSim = genFreeSim hor++-- | It's just like the function `freeSim` but the user can +-- activate some samples right in the code. If the third +-- element is @True@ the sample is played.+freeSimWith :: [(String, Sam, Bool)] -> Source Sam+freeSimWith = genFreeSimInits ver++-- | It's just like the function `freeSimWith` but the visual representation is horizontal.+-- That's why there is a prefix @h@.+hfreeSimWith :: [(String, Sam, Bool)] -> Source Sam+hfreeSimWith = genFreeSimInits hor++genFreeSim :: ([Gui] -> Gui) -> [(String, Sam)] -> Source Sam+genFreeSim gcat as = genFreeSimInits gcat $ fmap (\(a, b) -> (a, b, False)) as++genFreeSimInits :: ([Gui] -> Gui) -> [(String, Sam, Bool)] -> Source Sam+genFreeSimInits gcat as = source $ do+ (guis, ts) <- fmap unzip $ zipWithM toggle names initVals+ let res = groupToggles mean sams ts+ return (gcat guis, res)+ where + (names, sams, initVals) = unzip3 as++-- | The widget to toggle between several samples (aka `tog`gle). +-- The prefix `free` means no syncronization. the samples start to play when the button is pressed.+freeTog :: [(String, Sam)] -> Source Sam+freeTog = genFreeTog ver++-- | It's just like the function @freeTog@ but the visual representation is horizontal.+hfreeTog :: [(String, Sam)] -> Source Sam+hfreeTog = genFreeTog hor++genFreeTog :: ([Gui] -> Gui) -> [(String, Sam)] -> Source Sam+genFreeTog gcat as = source $ do+ (guis, writes, reads) <- fmap unzip3 $ mapM (flip setToggleSig False) names+ curRef <- newGlobalSERef (0 :: Sig)+ current <- readSERef curRef+ zipWithM_ (\w i -> w $ ifB (current ==* i) 1 0) writes ids+ zipWithM_ (\r i -> runEvt (snaps r) $ \x -> do+ when1 (sig x ==* 0 &&* current ==* i) $ do+ writeSERef curRef 0+ when1 (sig x ==* 1) $ do+ writeSERef curRef i + ) reads ids++ let res = groupToggles sum sams $ fmap (snaps . (\i -> ifB (current ==* i) 1 0)) ids+ return (gcat guis, res)+ where + (names, sams) = unzip as+ ids = fmap (sig . int) [1 .. length as]+++genSim :: ([Gui] -> Gui) -> Int -> [(String, Sam)] -> Source Sam+genSim gcat numBeats as = genSimInits gcat numBeats $ fmap (\(a, b) -> (a, b, False)) as++genSimInits :: ([Gui] -> Gui) -> Int -> [(String, Sam, Bool)] -> Source Sam+genSimInits gcat numBeats as = source $ do+ (guis, writes, reads) <- fmap unzip3 $ zipWithM setToggleSig names initVals+ curRefs <- mapM (const $ newGlobalSERef (0 :: Sig)) ids+ currents <- mapM readSERef curRefs+ zipWithM_ (\w val -> w val) writes currents+ let mkReaders bpm = zipWithM_ (\r ref -> runEvt (syncBpm (bpm / int numBeats) $ snaps r) $ \x -> do+ writeSERef ref (sig x)+ ) reads curRefs+ let res = bindBpm (\bpm x -> mkReaders bpm >> return x) $ groupToggles mean sams $ fmap snaps currents+ return (gcat guis, res)+ where + (names, sams, initVals) = unzip3 as+ ids = fmap (sig . int) [1 .. length as]++-- | A widget for playing several samples at the same time (aka `sim`ultaneously).+-- The first argument is about syncronization. +--+-- > sim n nameAndSamples+--+-- The samples are started only on every n'th beat.+-- The tempo is specified with rendering the sample (see the function @runSam@). +sim :: Int -> [(String, Sam)] -> Source Sam+sim = genSim ver++-- | It's just like the function @sim@ but the visual representation is horizontal.+-- That's why there is a prefix @h@. +hsim :: Int -> [(String, Sam)] -> Source Sam+hsim = genSim hor+++-- | It's just like the function `sim` but the user can +-- activate some samples right in the code. If the third +-- element is @True@ the sample is played.+simWith :: Int -> [(String, Sam, Bool)] -> Source Sam+simWith = genSimInits ver++-- | It's just like the function `hsimWith` but the visual representation is horizontal.+-- That's why there is a prefix @h@.+hsimWith :: Int -> [(String, Sam, Bool)] -> Source Sam+hsimWith = genSimInits hor+++genTog :: ([Gui] -> Gui) -> Int -> [(String, Sam)] -> Source Sam+genTog gcat numBeats as = fmap (\(g, x) -> (g, fst x)) $ genTogWithRef gcat numBeats as++genTogWithRef :: ([Gui] -> Gui) -> Int -> [(String, Sam)] -> Source (Sam, SERef Sig)+genTogWithRef gcat numBeats as = source $ do+ (guis, writes, reads) <- fmap unzip3 $ mapM (flip setToggleSig False) names+ curRef <- newGlobalSERef (0 :: Sig)+ current <- readSERef curRef+ zipWithM_ (\w i -> w $ ifB (current ==* i) 1 0) writes ids+ let mkReaders bpm = zipWithM_ (\r i -> runEvt (syncBpm (bpm / int numBeats) $ snaps r) $ \x -> do+ when1 (sig x ==* 0 &&* current ==* i) $ do+ writeSERef curRef 0 + when1 (sig x ==* 1) $ do+ writeSERef curRef i + ) reads ids++ let res = bindBpm (\bpm x -> mkReaders bpm >> return x) $ groupToggles sum sams $ fmap (snaps . (\i -> ifB (current ==* i) 1 0)) ids+ return (gcat guis, (res, curRef))+ where + (names, sams) = unzip as+ ids = fmap (sig . int) [1 .. length as]++-- | A widget to toggle playing of several samples. The switch +-- of the playing is synchronized with each n'th beat where+-- n is the first argument of the function.+tog :: Int -> [(String, Sam)] -> Source Sam+tog = genTog ver++-- | It's just like the function @tog@ but the visual representation is horizontal.+-- That's why there is a prefix @h@. +htog :: Int -> [(String, Sam)] -> Source Sam+htog = genTog hor++-- | The widget resembles the Ableton Live session view. +-- We create a matrix of samples. we can toggle the samples in +-- each row and we can start playing the whole row of samples.+--+-- > live n groupNames samples+--+-- The first argument is for synchroization. we can start samples+-- only on every n'th beat. The second argument gives names to the columns.+-- the length of the list is the number of columns. +-- the column represents samples that belong to the same group.+-- The third argument is a list of samples. It represents the matrix of samples+-- in row-wise fashion.+live :: Int -> [String] -> [Sam] -> Source Sam+live numBeats names sams = source $ do+ (gVols, vols) <- fmap unzip $ mapM defSlider $ replicate n "vol"+ (gs, xs) <- fmap unzip $ zipWithM (mkLiveRow numBeats) (zip names gVols) rows + let (sigs, refs) = unzip xs+ (gMaster, masterVol) <- defSlider "master"+ (g, proc) <- mkLiveSceneRow numBeats gMaster ids refs+ return $ (hor $ g : gs, bindBpm (\bpm asig -> proc bpm >> return asig) $ mul masterVol $ mean $ zipWith mul vols sigs)+ where + rows = transpose $ splitRows n sams+ ids = fmap (sig . int) [1 .. length (head rows)]+ n = length names++mkLiveRow :: Int -> (String, Gui) -> [Sam] -> Source (Sam, SERef Sig)+mkLiveRow numBeats (name, gVol) xs = genTogWithRef (\xs -> ver $ xs ++ [gVol]) numBeats (zip (name : repeat "") xs)++mkLiveSceneRow :: Int -> Gui -> [Sig] -> [SERef Sig] -> SE (Gui, D -> SE ())+mkLiveSceneRow numBeats gMaster ids refs = do + (guis, writes, reads) <- fmap unzip3 $ mapM (flip setToggleSig False) names+ curRef <- newGlobalSERef (0 :: Sig)+ current <- readSERef curRef+ zipWithM_ (\w i -> w $ ifB (current ==* i) 1 0) writes ids+ let mkReaders bpm = zipWithM_ (\r i -> runEvt (syncBpm (bpm / int numBeats) $ snaps r) $ \x -> do+ when1 (sig x ==* 0 &&* current ==* i) $ do+ writeSERef curRef 0 + mapM_ (flip writeSERef 0) refs+ when1 (sig x ==* 1) $ do+ writeSERef curRef i + mapM_ (flip writeSERef i) refs+ ) reads ids++ return (ver $ guis ++ [gMaster], mkReaders)+ where + names = take len $ fmap show [1 ..] + len = length ids++splitRows :: Int -> [a] -> [[a]]+splitRows n as + | length as < n = []+ | otherwise = take n as : splitRows n (drop n as)++defSlider :: String -> Source Sig+defSlider tag = slider tag (linSpan 0 1) 0.5++-- | It's just like the function @live@ but we can provide the list+-- of effects for each column. The double value specifies the mix+-- between dry and wet signals.+liveEf :: Int -> [String] -> [Sam] -> (Double, FxFun) -> [(Double, FxFun)] -> Source Sam+liveEf numBeats names sams masterEff effs = source $ do+ (gVols, vols) <- fmap unzip $ mapM defSlider $ replicate n "vol"+ (gEffs, effCtrls) <- fmap unzip $ + mapM (\(tag, initVal) -> slider tag (linSpan 0 1) initVal) $ zip (replicate n "eff") (fmap fst effs)+ let gCtrls = zipWith ctrlGui gEffs gVols+ (gs, xs) <- fmap unzip $ zipWithM (mkLiveRow numBeats) (zip names gCtrls) rows + let (sigs, refs) = unzip xs+ (gMaster, masterVol) <- defSlider "master"+ (gMasterEff, masterEffCtrl) <- slider "eff" (linSpan 0 1) (fst masterEff)+ (g, proc) <- mkLiveSceneRow numBeats (ctrlGui gMasterEff gMaster) ids refs+ return $ (hor $ g : gs, bindBpm (\bpm asig -> proc bpm >> return asig) $ + mul masterVol $ appEff (snd masterEff) masterEffCtrl $ + mean $ zipWith mul vols $ zipWith (uncurry appEff) (zip (fmap snd effs) effCtrls) sigs)+ where + rows = transpose $ splitRows n sams+ ids = fmap (sig . int) [1 .. length (head rows)]+ n = length names++ appEff f depth a = bindSam (\x -> fmap (\y -> y + (mul (1 - depth) x)) $ mul depth $ f x) a++ ctrlGui eff vol = sca 2.5 $ ver [eff, vol]++-- | It's useful to convert samples to signals an insert+-- them in the widget @mixer@.+mixSam :: String -> Bpm -> Sam -> (String, SE Sig2)+mixSam name bpm sam = (name, runSam bpm sam)++-- | Creates fx-unit from sampler widget.+--+-- > uisam name isOn bpm samWidget+uiSam :: String -> Bool -> D -> Source Sam -> Source FxFun+uiSam name onOff bpm sam = uiSig name onOff (joinSource $ mapSource (runSam bpm) sam)+ where + joinSource :: Source (SE Sig2) -> Source Sig2+ joinSource a = source $ do+ (g, mres) <- a+ res <- mres+ return (g, res)++-- | Adds gain slider on top of the widget. +addGain :: SigSpace a => Source a -> Source a+addGain x = source $ do+ (g, asig) <- x+ (gainGui, gain) <- slider "gain" (linSpan 0 1) 0.5+ return (ver [sca 0.15 gainGui, g], mul gain asig)