packages feed

Bang 0.1.0.6 → 0.1.1.0

raw patch · 8 files changed

+494/−10 lines, 8 filesdep +time

Dependencies added: time

Files

Bang.cabal view
@@ -1,5 +1,5 @@ name:                Bang-version:             0.1.0.6+version:             0.1.1.0 synopsis:            A Drum Machine DSL for Haskell description:            This library consists of a DSL for piecing together drum compositions. It uses a MIDI backend@@ -24,7 +24,10 @@                        Bang.Music,                        Bang.Music.Class,                         Bang.Music.Operators, -                       Bang.Music.Transform+                       Bang.Music.Transform,+                       Bang.Experimental.Live,+                       Bang.Experimental.SequentialDo,+                       Bang.Experimental.ConcurrentDo    other-modules:       Bang.Interpreter @@ -33,7 +36,8 @@                        transformers >=0.3 && <0.4,                        bifunctors >= 4 && <5,                        stm >= 2.4 && < 5,-                       hmidi >= 0.2.1 && < 3.0+                       hmidi >= 0.2.1 && < 3.0,+                       time >= 1.4.2 && < 1.5    hs-source-dirs:      src   default-language:    Haskell2010
README.md view
@@ -6,9 +6,214 @@ Bang interfaces with your system MIDI device in order to play drum compositions, directly written in and  interpreted by the Haskell programming language. -Example:+Currently, Bang only supports OSX Mavericks. Windows playback works, but is finnicky. In particular, if you interrupt a composition launched from a ghci interpreter session, the built-in MIDI synthesizer will complain about already being in use the next time you attempt to play something, until ghci is reloaded. This issue does not arise when playing songs from files. If you define a `main` function in an external file, and compile and run it, interrupting compositions should work fine. +### Installing++Installation of the library is simple.++```+> cabal update+> cabal install Bang+```++On Windows (*again, not recommended*), You should be good to go at this point. If you see **Using MIDI Device: Microsoft GS Wavetable Synth** when playing your first composition, you're set. If this happens but nothing plays, please contact me.++On OSX, some additional setup is required to output MIDI sounds. First, you'll need to download and install [SimpleSynth](http://notahat.com/simplesynth/). Next, you'll need to set up a MIDI IAC driver. Open Audio MIDI Setup (in `Applications -> Utilities`) and press ⌘2 (or go `Window -> Show MIDI Window`). You should see a slightly greyed out **IAC Driver** icon. Double click it, then check the box labeled "Device is online." Launch SimpleSynth and set the MIDI Source to "**IAC Driver Bus 1**" (or whatever you named your IAC driver) using the drop-down box at the top of the window. Audio from `Bang` should now feed into and play through SimpleSynth.++### Getting Started++Bang exports two main entry points for playing compositions, `bang` to play compositions once and stop, and `bangR`, to play compositions on repeat. In all of the following examples, `bang` can be replaced by `bangR`.++To play a single bass drum hit:+ ```haskell+> :m + Bang+> bang bd+```++##### Sequential Composition++To play two compositions sequentially, use `<>` (from `Data.Monoid`):++```haskell+> :m + Data.Monoid+> bang $ bd <> sn -- bass, then snare.+```++##### Rests++To add rests into compositions, use `rest`:++```haskell+> bang $ bd <> rest (1/4) <> bd -- bass, quarter rest, bass.+```++Many special cases of rests reside in [Bang.Interface.Base](https://github.com/5outh/Bang/blob/master/src/Bang/Interface/Base.hs). These include `qr` (quarter rest), `hr` (half), `er` (eighth), `wr` (whole) and more.++##### Parallel Composition++To play two compositions in parallel, use `><`:++```haskell+> bang $ bd >< sn -- bass and snare at the same time.+```++##### Repetition++To play a composition multiple times in sequence, use `#>`:++```haskell+> bang $ 4 #> bd -- play 4 bass drum hits in sequence+```++##### Changing Tempo++`bang` plays a composition at 120 BPM by default. To change the tempo, use `!>`, which expects a fraction as the first argument:++```haskell+> bang $ (1/2) !> (bd <> sn) -- play at half speed+```++A number of special cases of tempo-setting functions such as `double`, `quad`, `half`, and `triplets` reside in [Bang.Interface.Base](https://github.com/5outh/Bang/blob/master/src/Bang/Interface/Base.hs).++##### Changing Duration++By default, each primitive note in `Bang` has duration `1/4`. To set the duration for a composition, use `~~`:++```haskell+> bang $ 1 ~~ (bd <> bd) -- play two bass drum hits with a total duration of 1.+```++##### Polyrhythms++Composing polyrhythms can be done in three ways. The first, using `~=~`:++```haskell+-- play a 3/4 polyrhythm with bass drum triplets and snare quarter notes.+> bang $ (3, 3 #> bd) ~=~ (4, 4 #> sn)+```++The other two ways are by using `~=` and `=~`. These are slightly more general than the above case, and can be used for more than just polyrhythms. `a ~= b` smashes (or elongates) `b` into the duration of `a`, while `=~` does the same thing, but in the other direction. For example, the polyrhythm above can be more concisely represented like this:++```haskell+> bang $ (3 #> bd) =~ (4 #> sn)+```++To play the same rhythm in `3/4` duration, just use the other operator:++```haskell+> bang $ (3 #> bd) ~= (4 #> sn)+```++##### Slicing Compositions++To play only the first part of a composition, use `takeDur` or `<<~`. To chop off a section of the end of a composition, use `dropDur` or `~>>`:++```haskell+> bang $ (1/4) <<~ ( 2 #> (bd <> sn) ) -- Play only `bd`+> bang $ (1/4) ~>> ( 2 #> (bd <> sn) ) -- Play bd, sn, bd+```++##### Silencing++To instead silence parts of a composition, use `hushFor` (`~@>`) to silence from the beginning, or `hushFrom` (`<@~`) to silence from some point until the end of a composition:++```haskell+> bang $ (1/4) ~@> ( 2 #> (bd <> sn) ) -- Play a quarter rest, then bd, sn, bd+> bang $ (1/4) <@~ ( 2 #> (bd <> sn) ) -- Play `bd`, then a 3/4 rest.+```++##### Duration Normalization++We can normalize a list of compositions to the same duration and play them sequentially using `normalize` or `<!>`:++```haskell+-- play each composition sequentially with duration 1.+> bang $ 1 <!> [bd, 3 #> sn, 5 #> hc]+```++We can do the same, but play each in parallel using `normalizeC` (C for 'Concurrent') or `>!<`:++```haskell+-- play each composition concurrently with duration 1.+> bang $ 1 >!< [bd, 3 #> sn, 5 #> hc]+```++##### Mapping Over Composition Lists++We can map an operator over a list of compositions using `>>~`:++```haskell+-- Play each note in the list twice, sequentially.+> bang $ (2 #>) >>~ [sn, bd, hc]+```++#### Other Transformations++##### Reversal++`reverseMusic` does just what you'd expect:++```haskell+> bang $ reverseMusic $ bd <> sn -- snare, then bass.+```++##### Mirrors++`mirror` plays a composition forward, then backward. `mirrorR` plays it backwards, then forwards:++```haskell+> bang $ mirror  $ bd <> sn -- bd, sn, sn, bd+> bang $ mirrorR $ bd <> sn -- sn, bd, bd, sn+```++##### Cross++`cross` plays a composition both forward and backward at the same time:++```haskell+> bang $ cross $ bd <> sn -- (bd & sn), (bd & sn)+```++##### Repeating++`rep` repeats a composition ad infinitum:++```haskell+> bang $ rep bd -- bd, bd, bd, bd ...+```++##### Measures++`m4` is a convenience constructor for 4-element compositions (useful for piecing together 4-element measures):++```haskell+> bang $ m4 bd hc bd sn -- bd, hc, bd, sn+```++##### Note About Monoids++Compositions form two monoids: one under `<>` (sequential composition) and one under `><` (parallel composition). To avoid wrapping everything in newtypes, the `<>` monoid is the 'real' one, and the `><` one uses similar names to the "real" monoid names.++Of particular note are the "concat" functions. `mconcat` plays a list of compositions in sequence, while `cconcat` plays a list of compositions in parallel:++```haskell+> bang $ mconcat [bd, sn, hc, sn] -- bd, sn, hc, sn in sequence+> bang $ cconcat [bd, sn, hc]     -- bd, sn, hc all at once +```++##### Extras++All of the operators reside in [Bang.Music.Operators](https://github.com/5outh/Bang/blob/master/src/Bang/Music/Operators.hs), with most underlying implementations (and plain text functions) in [Bang.Music.Transform](https://github.com/5outh/Bang/blob/master/src/Bang/Music/Transform.hs).++All of the primitive sounds (such as `bd`, `sn` and `hc` are implemented in [Bang.Interface.Drum](https://github.com/5outh/Bang/blob/master/src/Bang/Interface/Drum.hs). This includes all of the MIDI percussion sounds, with the more common ones having shortform and longform names. All of these can be used in Bang compositions.++Finally, the full documentation is available [on Hackage](https://hackage.haskell.org/package/Bang).++##### An Extended Example++```haskell -- | The first few measures of 'Toxicity' by System of a Down. toxicityIntro =   let sh = sn >< hc -- snare and closed hi-hat combo@@ -33,3 +238,7 @@            ]           ] ) ```++### Acknowlegements++A lot of inspiration and guidance on the implementation of Bang comes from the [Haskell School of Music](http://haskell.cs.yale.edu/euterpea/haskell-school-of-music/) and Paul Hudak's [very interesting paper](http://cpsc.yale.edu/sites/default/files/files/tr1259.pdf). [Tidal by Alex McLean](http://yaxu.org/tidal/) is also notable as inspiration. The goal of Bang is to produce a small subset of these things that works well for something very specific: composing drum-only beats.
src/Bang.hs view
@@ -19,6 +19,7 @@ , defaultOptions , module Bang.Music , module Bang.Interface+, (<>) ) where  import Control.Monad@@ -27,6 +28,7 @@ import Control.Concurrent import Data.Monoid import System.MIDI+import System.Info  import Bang.Music import Bang.Interface@@ -78,8 +80,12 @@ -- | 'play' with specified 'Options' playWith :: Options -> Connection -> Music Dur PercussionSound -> IO () playWith (Options oBpm oTempo) conn song = do+  -- Add a dummy note at the end 'cause Windows doesn't play the last one for some reason.+  -- This is stupid, but windows is generally unsupported anyway.+  let song' | os == "mingw32" || os == "mingw" = song <> bd +            | otherwise = song   start conn-  evalStateT runComposition (conn, interpret (bpm oBpm $ tempo oTempo song))+  evalStateT runComposition (conn, interpret (bpm oBpm $ tempo oTempo song'))   close conn  -- | 'play' a composition over a given 'Connection'@@ -101,5 +107,5 @@       when (s < t) $ do         put (conn, xs)         lift $ send conn ev-      lift $ threadDelay 1000+      lift $ threadDelay 250       runComposition
+ src/Bang/Experimental/ConcurrentDo.hs view
@@ -0,0 +1,21 @@+{-|+Module      : Bang.Experimental.SequentialDo+Description : Experimental module for concurrent music composition with do-notation+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 you to compose music with `do` notation,+representing concurrent application, e.g. @do{hc; bd} = hc >< bd@+-}+{-# LANGUAGE RebindableSyntax, NoImplicitPrelude #-}+module Bang.Experimental.ConcurrentDo((>>), module Bang) where++import Bang+import Prelude hiding ((>>))+import Data.Monoid++(>>) :: Music dur a -> Music dur a -> Music dur a+(>>) = (><)
+ src/Bang/Experimental/Live.hs view
@@ -0,0 +1,94 @@+{-| +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
+ src/Bang/Experimental/SequentialDo.hs view
@@ -0,0 +1,21 @@+{-|+Module      : Bang.Experimental.SequentialDo+Description : Experimental module for sequential music composition with do-notation+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 you to compose music with `do` notation,+representing sequential application, e.g. @do{hc; bd} = hc <> bd@+-}+{-# LANGUAGE RebindableSyntax, NoImplicitPrelude #-}+module Bang.Experimental.SequentialDo((>>), module Bang) where++import Bang+import Prelude hiding ((>>))+import Data.Monoid++(>>) :: Monoid m => m -> m -> m+(>>) = (<>)
src/Bang/Interface/Base.hs view
@@ -13,6 +13,7 @@  import Bang.Music.Class import Data.Monoid+import Data.Ratio  -- | 'Rest' for a given duration. rest :: Dur -> Music Dur a@@ -30,10 +31,11 @@ tempo :: Rational -> Music a b -> Music a b tempo n = Modify (Tempo (1/n)) --- | Convenience function for concatenating four compositions together--- sequentially. Most general type signature \'cause why not?-m4 :: Monoid a => a -> a -> a -> a -> a-m4 a b c d = mconcat [a, b, c, d]+-- | Set the time signature of a composition+-- @n@ is the beat unit, @d@ is the number of beats per measure.+-- For example, @ts 4 4 ==@ Common time+ts :: Rational -> Rational -> Music a b -> Music a b+ts n d = Modify (Tempo (d/n))  -- | Quadruple the tempo of a composition. quad :: Music a b -> Music a b@@ -86,3 +88,99 @@ -- | Whole rest wr :: Music Dur a wr = rest 1++-- Exported separately because it'll be nice to have for Drum+dots :: Int -> Dur+dots n = 2 - (1 / (2 ^ n))++dottedRest :: Int -> Dur -> Music Dur a+dottedRest n d = rest (d * (dots n))++-- | Shorthand for @dottedRest@+dr :: Int -> Dur -> Music Dur a+dr = dottedRest++-- | Constructor for a singly-dotted rest+oneDotRest :: Dur -> Music Dur a+oneDotRest = dottedRest 1++-- | Eighth Dotted Rest+edr :: Music Dur a+edr = oneDotRest (1/8)++-- | Quarter Dotted Rest+qdr :: Music Dur a+qdr = oneDotRest (1/4)++-- | Half Dotted Rest+hdr :: Music Dur a+hdr = oneDotRest (1/2)++-- | Whole Dotted Rest+wdr :: Music Dur a+wdr = oneDotRest 1++-- |Sequence `k` compositions together without the need for lists. +-- `m` corresponds to `m` in `mappend`, `mconcat`, etc.+m2 :: Monoid a => a -> a -> a+m2 a b = a <> b+m3 :: Monoid a => a -> a -> a -> a+m3 a b c = mconcat [a, b, c]+m4 :: Monoid a => a -> a -> a -> a -> a+m4 a b c d = mconcat [a, b, c, d]+m5 :: Monoid a => a -> a -> a -> a -> a -> a+m5 a b c d e = mconcat [a, b, c, d, e]+m6 :: Monoid a => a -> a -> a -> a -> a -> a -> a+m6 a b c d e f =  mconcat [a, b, c, d, e, f]+m7 :: Monoid a => a -> a -> a -> a -> a -> a -> a -> a+m7 a b c d e f g = mconcat [a, b, c, d, e, f, g]+m8 :: Monoid a => a -> a -> a -> a -> a -> a -> a -> a -> a+m8 a b c d e f g h = mconcat [a, b, c, d, e, f, g, h]+m9 :: Monoid a => a -> a -> a -> a -> a -> a -> a -> a -> a -> a+m9 a b c d e f g h i = mconcat [a, b, c, d, e, f, g, h, i]+m10 :: Monoid a => a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a+m10 a b c d e f g h i j = mconcat [a, b, c, d, e, f, g, h, i, j]+m11 :: Monoid a => a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a+m11 a b c d e f g h i j k = mconcat [a, b, c, d, e, f, g, h, i, j, k]+m12 :: Monoid a => a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a -> a+m12 a b c d e f g h i j k l = mconcat [a, b, c, d, e, f, g, h, i, j, k, l]++-- |Play `k` compositions concurrently without the need for lists.+-- `c` corresponds to `c` in `cappend`, `cconcat`, etc.+c2 :: Num d => Music d a -> Music d a -> Music d a+c2 a b = a <> b+c3 :: Num d => Music d a -> Music d a -> Music d a -> Music d a+c3 a b c = cconcat [a, b, c]+c4 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +            -> Music d a+c4 a b c d = cconcat [a, b, c, d]+c5 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +              -> Music d a -> Music d a+c5 a b c d e = cconcat [a, b, c, d, e]+c6 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +            -> Music d a -> Music d a -> Music d a+c6 a b c d e f =  cconcat [a, b, c, d, e, f]+c7 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +            -> Music d a -> Music d a -> Music d a -> Music d a+c7 a b c d e f g = cconcat [a, b, c, d, e, f, g]+c8 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +            -> Music d a -> Music d a -> Music d a -> Music d a +            -> Music d a+c8 a b c d e f g h = cconcat [a, b, c, d, e, f, g, h]+c9 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +            -> Music d a -> Music d a -> Music d a -> Music d a +            -> Music d a -> Music d a+c9 a b c d e f g h i = cconcat [a, b, c, d, e, f, g, h, i]+c10 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +             -> Music d a -> Music d a -> Music d a -> Music d a +             -> Music d a -> Music d a -> Music d a+c10 a b c d e f g h i j = cconcat [a, b, c, d, e, f, g, h, i, j]+c11 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +             -> Music d a -> Music d a -> Music d a -> Music d a +             -> Music d a -> Music d a -> Music d a -> Music d a+c11 a b c d e f g h i j k = cconcat [a, b, c, d, e, f, g, h, i, j, k]+c12 :: Num d => Music d a -> Music d a -> Music d a -> Music d a +             -> Music d a -> Music d a -> Music d a -> Music d a +             -> Music d a -> Music d a -> Music d a -> Music d a +             -> Music d a+c12 a b c d e f g h i j k l = cconcat [a, b, c, d, e, f, g, h, i, j, k, l]
src/Bang/Interface/Drum.hs view
@@ -72,9 +72,40 @@ drum :: PercussionSound -> Dur -> Music Dur PercussionSound drum ps d = Prim (Note d ps) +dottedDrum :: Int -> PercussionSound -> Dur -> Music Dur PercussionSound+dottedDrum n ps d = drum ps (d * dots n)++oneDotDrum :: PercussionSound -> Dur -> Music Dur PercussionSound+oneDotDrum = dottedDrum 1 ++ed :: PercussionSound -> Music Dur PercussionSound+ed ps = drum ps (1/8)+ -- | Simple constructor for a quarter-note drum hit. qd :: PercussionSound -> Music Dur PercussionSound qd ps = drum ps (1/4)++hd :: PercussionSound -> Music Dur PercussionSound+hd ps = drum ps (1/2)++wd :: PercussionSound -> Music Dur PercussionSound+wd ps = drum ps 1++-- | Eighth Dotted Drum+edd ::  PercussionSound -> Music Dur PercussionSound+edd ps = oneDotDrum ps (1/8)++-- | Quarter Dotted Drum+qdd ::  PercussionSound -> Music Dur PercussionSound+qdd ps = oneDotDrum ps (1/4)++-- | Half Dotted Drum+hdd ::  PercussionSound -> Music Dur PercussionSound+hdd ps = oneDotDrum ps (1/2)++-- | Whole Dotted Drum+wdd :: PercussionSound -> Music Dur PercussionSound+wdd ps = oneDotDrum ps 1  -- | Get the MIDI offset number for a 'PercussionSound' toMIDINum :: PercussionSound -> Int