packages feed

essence-of-live-coding-pulse-0.2.3: src/LiveCoding/Pulse.hs

{-# LANGUAGE Arrows #-}
module LiveCoding.Pulse where

-- base
import Control.Arrow as X
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

-- essence-of-live-coding
import LiveCoding

type PulseT m = WriterT (Sum Float) m

type PulseCell m a b = Cell (PulseT m) a b

-- | Compose with this cell to play a sound sample.
addSample :: Monad m => PulseCell m Float ()
addSample = arr Sum >>> arrM tell

-- | 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
    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

-- | A sawtooth, or triangle wave, generator,
--   outputting a sawtooth wave with the given input as frequency.
sawtooth :: (Monad m, Data a, RealFloat a) => Cell m a a
sawtooth = wrapIntegral

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

-- | 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 <- wrapIntegral -< f
  returnA -< sin $ 2 * pi * phase

-- | 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
  | B
  | C
  | Cis
  | D
  | Dis
  | E
  | F
  | Fis
  | G
  | 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)