{-# LANGUAGE CPP, PackageImports #-} import Control.Monad import Data.IORef import Data.Functor #ifdef CABAL import qualified "threepenny-gui" Graphics.UI.Threepenny as UI import "threepenny-gui" Graphics.UI.Threepenny.Core #else import qualified Graphics.UI.Threepenny as UI import Graphics.UI.Threepenny.Core #endif import Paths import System.FilePath {----------------------------------------------------------------------------- Configuration ------------------------------------------------------------------------------} bars = 4 beats = 4 defaultBpm = 120 bpm2ms :: Int -> Int bpm2ms bpm = ceiling $ 1000*60 / fromIntegral bpm -- NOTE: Samples taken from "conductive-examples" instruments = words "kick snare hihat" loadInstrumentSample w name = do static <- getStaticDir loadFile w "audio/wav" (static name <.> "wav") {----------------------------------------------------------------------------- Main ------------------------------------------------------------------------------} main :: IO () main = do static <- getStaticDir startGUI Config { tpPort = 10000 , tpCustomHTML = Nothing , tpStatic = static } setup setup :: Window -> IO () setup w = void $ do return w # set title "Ha-ha-ha-ks-ks-ks-ha-ha-ha-ell-ell-ell" elBpm <- UI.input # set value (show defaultBpm) elTick <- UI.span (kit, elInstruments) <- mkDrumKit w let status = grid [[UI.string "BPM:" , element elBpm] ,[UI.string "Beat:", element elTick]] getBody w #+ [UI.div #. "wrap" #+ (status : map element elInstruments)] timer <- UI.timer # set UI.interval (bpm2ms defaultBpm) refBeat <- newIORef 0 -- play sounds on timer events on UI.tick timer $ const $ void $ do -- get and increase beat count beat <- readIORef refBeat writeIORef refBeat $ (beat + 1) `mod` (beats * bars) element elTick # set text (show $ beat + 1) -- play corresponding sounds sequence_ $ map (!! beat) kit -- allow user to set BPM on (domEvent "livechange") elBpm $ const $ void $ do bpm <- read <$> get value elBpm return timer # set UI.interval (bpm2ms bpm) -- star the timer UI.start timer type Kit = [Instrument] type Instrument = [Beat] type Beat = IO () -- play the corresponding sound mkDrumKit :: Window -> IO (Kit, [Element]) mkDrumKit w = unzip <$> mapM (mkInstrument w) instruments mkInstrument :: Window -> String -> IO (Instrument, Element) mkInstrument window name = do elCheckboxes <- sequence $ replicate bars $ sequence $ replicate beats $ UI.input # set UI.type_ "checkbox" url <- loadInstrumentSample window name elAudio <- UI.audio # set (attr "preload") "1" # set UI.src url let play box = do checked <- get UI.checked box when checked $ audioPlay elAudio beats = map play . concat $ elCheckboxes elGroups = [UI.span #. "bar" #+ map element bar | bar <- elCheckboxes] elInstrument <- UI.div #. "instrument" #+ (element elAudio : UI.string name : elGroups) return (beats, elInstrument)