essence-of-live-coding-pulse 0.2.0.1 → 0.2.1
raw patch · 3 files changed
+88/−62 lines, 3 filesdep ~essence-of-live-coding
Dependency ranges changed: essence-of-live-coding
Files
- essence-of-live-coding-pulse.cabal +2/−3
- src/LiveCoding/Pulse.hs +86/−30
- src/LiveCoding/Pulse/GHCi.hs +0/−29
essence-of-live-coding-pulse.cabal view
@@ -1,5 +1,5 @@ name: essence-of-live-coding-pulse-version: 0.2.0.1+version: 0.2.1 synopsis: General purpose live coding framework - pulse backend description: essence-of-live-coding is a general purpose and type safe live coding framework.@@ -28,10 +28,9 @@ library exposed-modules: LiveCoding.Pulse- LiveCoding.Pulse.GHCi build-depends: base >= 4.11 && < 5- , essence-of-live-coding >= 0.2.0.1+ , essence-of-live-coding >= 0.2.1 , transformers >= 0.5 , pulse-simple >= 0.1 , foreign-store >= 0.2
src/LiveCoding/Pulse.hs view
@@ -6,9 +6,12 @@ import Control.Concurrent import Control.Monad (forever) import Control.Monad.Fix+import Data.Monoid (getSum, Sum(Sum)) -- transformers+import Control.Monad.Trans.Class (MonadTrans(lift)) import Control.Monad.Trans.Reader+import Control.Monad.Trans.Writer.Strict -- pulse-simple import Sound.Pulse.Simple@@ -16,63 +19,110 @@ -- essence-of-live-coding import LiveCoding -type PulseCell = Cell IO () Float+type PulseT m = WriterT (Sum Float) m -playPulseCell :: PulseCell -> IO (MVar PulseCell)-playPulseCell pulseCell = do- var <- newMVar pulseCell- pulseClient <- simpleNew- Nothing- "example"- Play- Nothing- "this is an example application"- (SampleSpec (F32 LittleEndian) 44100 1)- Nothing- Nothing- forkIO $ forever $ do- cell <- takeMVar var- (samples, cell') <- steps cell $ replicate 1024 ()- simpleWrite pulseClient samples- putMVar var cell'- return var+type PulseCell m a b = Cell (PulseT m) a b --- TODO Generalisable-updatePulse :: MVar PulseCell -> PulseCell -> IO ()-updatePulse var newCell = do- oldCell <- takeMVar var- putMVar var $ hotCodeSwapCell newCell oldCell+-- | Compose with this cell to play a sound sample.+addSample :: Monad m => PulseCell m Float ()+addSample = arr Sum >>> arrM tell --- Returns the sum between -1 and 1+-- | Globally fix the sample rate to 48000 samples per second.+sampleRate :: Num a => a+sampleRate = 48000++{- | Create a pulse server backend handle.++Currently, this is always mono,+but with a future release of @pulse-simple@,+this might be configurable.+-}+pulseHandle :: Handle IO Simple+pulseHandle = Handle+ { create = simpleNew+ Nothing+ "example"+ Play+ Nothing+ "this is an example application"+ (SampleSpec (F32 LittleEndian) sampleRate 1)+ Nothing+ Nothing+ , destroy = simpleFree+ }++{- | Run a 'PulseCell' with a started pulse backend.++Currently, this is synchronous and blocking,+i.e. the resulting cell will block until the backend buffer is nearly empty.++This performs several steps of your cell at a time,+replicating the input so many times.+-}+pulseWrapC+ :: Int+ -- ^ Specifies how many steps of your 'PulseCell' should be performed in one step of 'pulseWrapC'.+ -> PulseCell IO a b+ -- ^ Your cell that produces samples.+ -> Cell (HandlingStateT IO) a [b]+pulseWrapC bufferSize cell = proc a -> do+ simple <- handling pulseHandle -< ()+ samplesAndBs <- resampleList $ liftCell $ runWriterC cell -< replicate bufferSize a+ let (samples, bs) = unzip samplesAndBs+ samples' = getSum <$> samples+ arrM $ lift . uncurry simpleWrite -< samples' `seq` bs `seq` (simple, samples')+ returnA -< bs++{- | Returns the sum of all incoming values,+and wraps it between -1 and 1.++This is to prevent floating number imprecision when the sum gets too large.+-} wrapSum :: (Monad m, Data a, RealFloat a) => Cell m a a wrapSum = Cell { cellState = 0 , cellStep = \accum a -> let- (_, accum') = properFraction $ accum + a+ (_, accum') = properFraction $ accum + a in return (accum', accum') } +-- | Like 'wrapSum', but as an integral, assuming the PulseAudio 'sampleRate'.+wrapIntegral :: (Monad m, Data a, RealFloat a) => Cell m a a+wrapIntegral = arr (/ sampleRate) >>> wrapSum+ modSum :: (Monad m, Data a, Integral a) => a -> Cell m a a modSum denominator = Cell { cellState = 0 , cellStep = \accum a -> let accum' = (accum + a) `mod` denominator in return (accum', accum') } - clamp :: (Ord a, Num a) => a -> a -> a -> a clamp lower upper a = min upper $ max lower a -osc :: (Data a, RealFloat a, MonadFix m) => Cell (ReaderT a m) () a+-- | A sine oscillator.+-- Supply the frequency via the 'ReaderT' environment.+-- See 'osc'' and 'oscAt'.+osc :: (Data a, RealFloat a, Monad m) => Cell (ReaderT a m) () a osc = proc _ -> do f <- constM ask -< ()- phase <- wrapSum -< f / 44100+ phase <- wrapIntegral -< f returnA -< sin $ 2 * pi * phase -osc' :: (Data a, RealFloat a, MonadFix m) => Cell m a a+-- | A sine oscillator, at a fixed frequency.+oscAt :: (Data a, RealFloat a, Monad m) => a -> Cell m () a+oscAt = flip runReaderC osc++-- | A sine oscillator, at a frequency that can be specified live.+osc' :: (Data a, RealFloat a, Monad m) => Cell m a a osc' = proc a -> do runReaderC' osc -< (a, ()) +{- | A basic musical note (western traditional notation, german nomenclature).++Assumes equal temperament and removes enharmonic equivalents,+i.e. there is only Dis (= D sharp) but not Eb (= E flat).+-} data Note = A | Bb@@ -88,9 +138,15 @@ | Gis deriving (Enum, Show) +-- | Calculate the frequency of a note,+-- with 'A' corresponding to 220 Hz. f :: Note -> Float f note = 220 * (2 ** (fromIntegral (fromEnum note) / 12)) +-- | Transpose a frequency an octave higher, i.e. multiply by 2. o :: Float -> Float o = (* 2) +-- | Transpose a frequency an octave lower, i.e. divide by 2.+oB :: Float -> Float+oB = (/ 2)
− src/LiveCoding/Pulse/GHCi.hs
@@ -1,29 +0,0 @@-module LiveCoding.Pulse.GHCi where---- base-import Control.Concurrent---- foreign-store-import Foreign.Store---- essence-of-live-coding-import LiveCoding.LiveProgram---- essence-of-live-coding-pulse-import LiveCoding.Pulse--livepulse "" = livepulse "pulseCell"-livepulse pulseCell = return $ unlines- [ "var <- playPulseCell " ++ pulseCell- , "savePulse var"- ]-livereloadpulse "" = livereloadpulse "pulseCell"-livereloadpulse pulseCell = return $ unlines- [ ":reload"- , "var <- loadPulse"- , "updatePulse var " ++ pulseCell- ]-loadPulse :: IO (MVar PulseCell)-loadPulse = readStore (Store 0)-savePulse :: MVar PulseCell -> IO ()-savePulse var = writeStore (Store 0) var