Aelve Codesearch

grep over package repositories
Bang-0.1.1.1
src/Bang/Experimental/Live.hs
{-| 
Module      : Bang.Experimental.Live
Description : Experimental module for live coding with Bang
Copyright   : (c) Benjamin Kovach, 2014
License     : MIT
Maintainer  : bkovach13@gmail.com
Stability   : experimental
Portability : Mac OSX

An experimental alternative to the base `Bang` module that allows "live coding," a la Overtone or Tidal, via a ghci session.
Note: Very finnicky, pull requests welcome at https://github.com/5outh/Bang.

The following works in GHCI if you fork off @run@ into a new thread
and @readIORef counter@.
We can use an unsafe global variable like this to hold the duration to wait
until the next Bang composition should be played.
Right now, the aim is to be able to *replace* compositions being played during
runtime, but eventually I think this could be extended to include multiple
compositions being played at runtime, and turned on and off at will
with calls to @killThread@, etc. I want to make this more generic than making
everyone learn about concurrency and stuff, but as a first pass getting it working
with the existing concurrency mechanisms is the goal.
-}
module Bang.Experimental.Live where

import Control.Concurrent
import System.IO.Unsafe
import Data.IORef
import Control.Monad(forever, when)
import Data.Time.Clock.POSIX
import Control.Applicative((<$>))

import Bang

waitTime :: IORef Int
waitTime = unsafePerformIO (newIORef 0)

metronome :: MVar ThreadId
metronome = unsafePerformIO newEmptyMVar

-- |Bang live from a GHCi session. Note that the @ThreadId@ involved must be referenced directly
--   in order to replace or add to the currently running track. 
--
-- Example:
--
-- > track <- bangL bd
bangL :: Music Dur PercussionSound -> IO ThreadId
bangL = bangLWith defaultOptions

-- |Bang live with specified options.
bangLWith :: Options -> Music Dur PercussionSound -> IO ThreadId
bangLWith (Options oBpm oTempo) m = do
  startTime <- round . (*1000000) <$> getPOSIXTime
  let spb     = (60 :: Float) / (fromIntegral oBpm)
      beatLen = round $ spb * 1000000 -- length of a single beat, in ns

  -- constantly update waitTime
  met <- forkIO $ forever $ do
    time <- round . (*1000000) <$> getPOSIXTime
    writeIORef waitTime (beatLen - (time `mod` beatLen))

  -- forkIO the music and return threadId
  forkIO $ bangR m

-- |Kill a thread playing music and replace it with a new composition.
-- 
-- Example:
--  
-- @
--   m1 <- bangL hc 
--   m2 <- m1 `killThen` (bd <> hc <> bd <> bd)
-- @
killThen :: ThreadId
          -> Music Dur PercussionSound
          -> IO ThreadId
killThen tid m = do
  ref <- readIORef waitTime
  killThread tid
  forkIO $ threadDelay ref >> bangR m

-- |Add another track to play concurrently with the currently playing track.
--
-- Example:
--  
-- @
--   m1 <- bangL hc 
--   m2 <- m1 `addTrack` (bd <> hc <> bd <> bd)
-- @
addTrack :: ThreadId
         -> Music Dur PercussionSound
         -> IO ThreadId
addTrack tid m = do
  ref <- readIORef waitTime
  forkIO $ threadDelay ref >> bangR m