# Neat drum machine function

**URL:** <https://uzu.lurk.org/t/neat-drum-machine-function/6216>\
**Category:** TidalCycles\
**Tags:** questions\
**Created:** [4 September 2026 21:12 UTC](https://uzu.lurk.org/t/neat-drum-machine-function/6216 "2026-09-04T21:12:14Z")\
**Posts on this page:** 1\
**Page:** 1

<div class="post-metadata">

**Author:** ![ludens](https://uzu.lurk.org/user_avatar/uzu.lurk.org/ludens/32/3028_2.png) [@ludens](https://uzu.lurk.org/u/ludens)\
**Post date:** [4 September 2026 21:12 UTC](https://uzu.lurk.org/t/neat-drum-machine-function/6216/1 "2026-09-04T21:12:14Z")

</div>

I ran into a situation with my midi drum machine where I wanted to be able to set up a `grp` but have the first parameter set a few different "default" parameters.

I made a function `grpWith` that works like `grp` but allows one to combine repeated values  
with a specified combination function.

```hs
-- Might have some missing imports
import Text.Read (readMaybe)
import qualified Data.Map.Strict as Map
import Data.Maybe (fromMaybe)
import Sound.Tidal.Pattern (Value, Pattern, ControlPattern, ValueMap)
import Sound.Tidal.Utils (wordsBy)

-- | Like @grp@, but allows each part to create multiple control values
-- Combined with the union function `u`
grpWith :: (Value -> Value -> Value) -> [String -> ValueMap] -> Pattern String -> ControlPattern
grpWith u fs p = splitby <$> p
  where
    splitby :: String -> ValueMap
    splitby s = Map.unionsWith u $ map (\(v, f) -> f v) $ zip (split s) fs
    split :: String -> [String]
    split = wordsBy (== ':')

type ParamList = [(String, Value)]

drumMachine :: [(String, ParamList)] -> Pattern String -> ControlPattern
drumMachine voices = grpWith (fNum2 (*) (*)) [base , mF "legato" , mF "gain"]
  where
    -- Create base ValueMap from the voice table
    base :: String -> ValueMap
    base voice = fromMaybe Map.empty $ do
      baseParams <- lookup voice voices
      return $ Map.fromList baseParams

-- | Control the drumkit with patterns like `"bd hh hh:0.5 sd . bd sd"`
drums :: Pattern String -> ControlPattern
drums pat = drumMachine voices pat # sound "midi" # release 0 # n "0"
  where
    params c l g =
      [("midichan", (VI c)), ("gain", (VF (g*2.0))), ("legato", (VF l))]
    voices =
      [ ("bd", params 0 0.4 1)
      , ("sd", params 1 0.1 1)
      , ("hh", params 2 0.1 1)
      ]

```

What's really cool is I can have a default gain and legato that are tuned to work well with the quirks of my [drum machine](https://somasynths.com/pulsar-23/), and be able to _multiply_ the values after `:` against the defaults!

This way I still have the flexibility to combine and override as usual, but gotta be mindful to use  
`|*` if I want to preserve the behavior when composing patterns.

This should be flexible enough to work with notes instead of midi channels for each voice, but you'd have to update both `drumMachine` and `drums`

* * *

Shared under terms of AGPLv3.0.
