The Grid

The Grid

Compact percussion for eurorack inspired from Grids, by émilie gillet, one of my favorite modules.


an example setup

I’ve spent a lot of time considering percussion for eurorack, and this is essentially where I am. I wanted to post a basic release of this script before it moved too far into the past. I’ve actually evolved this script into a new version with uses a 2hp Route to multiplex additional channels, but the basic script works well and may provide you some inspiration of your own. (I’m going to include a number of ideas for how to adapt this to different uses, including without the TXi, below.)

A key motivator in approaching this script is knowability. Grids offers a staggering amount of possibility in its design, but presents challenges to performance usage. However, many of the underlying ideas are useful to managing complex live systems!

The general idea is that you have patterns mapped to to the bottom knob of the TXi. Unlike Grids, there is no attempt made to blend between patterns and there is only one dimension of navigation. The patterns are created by hand. Please experiment with your own! :slight_smile:

Thoughts on pattern organization:

I primarily make techno-y stuff, so I have square 4/4 style beat variations to the left of noon, and more asymmetrical percussion to the right of noon. Noon is vanilla 4 to the floor. You can have more than the 5 patterns that I have specified, but a lower number will make the knobs more knowable. Obviously feel free to change your notions about how to organize them or disregard them, but I’d encourage you to keep something safe at a fixed position so you can find it easily (at the noon, min, or max positions).

Unnecessary knowledge about Grids:

Part of the reason I wanted to make this, is that even after spending considerable time with Grids, I found it too risky to navigate experimentally in a live context - all of my music is improvised. I considered modifying the firmware, but the code for Grids is complex, and the patterns are derived mathematically from her research on percussion. Each element in the 16 canonical patterns of Grids (which are used for blending) has 256 increments of nuance, which means even very small changes to the channel levels can produce very different patterns. I’ve reduced resolution to 16 increments to make the patterns easier to design and explore.

Requirements

crow, TXi

(thoughts below if you want to try without a TXi)

Documentation

crow

– in1: 24pqn clock input
– in2: reset

– out1: bass drum
– out2: snare drum / clap
– out3: closed high hat
– out4: random event clock triggers

txi

– knob1: kick amount
– knob2: snare amount
– knob3: hats amount
– knob4: pattern

Load the script on to crow. Wiggle some knobs! It should work as written! Adjusting any knob to it’s max position will roll the channel.

What is boost?

You will see a set of variables for boost. Boost creates a variation in the indicated measure by adding the boost value to whichever knobs you indicate in boostMap. For any channel with “boost” enabled in the boost map, for the measure specified by boostMeasure, the boostValue will be added to the channels’ knob values. This is a way to create some interest in your patterns without having to constantly attend to the knobs, by default the 4th measure will add a little extra density to the kick and snare patterns, as that’s a safe and common way to add some interest.

Considerations for case arrangement:

I find it helpful to have the TXi at the left most edge of my case. It’s also a good idea to acquire the wire and clips to make i2c cables so you can manage your lengths as you see fit based on your module choices and arrangements. I find it best to either have the crow below or immediately next to the percussion modules, as in the example image above to minimize cable run lengths.

Script

Note: I haven’t actually run this version of the script in a bit, so I’ve left the debugging comments I had had in place in case I’ve overlooked something. This uses the newer crow 2.0 ii events, but may stop working if those protocols change. I’ll try to keep this little dude up to date.

--- the grid - 0.5.1
-- author: Clayton Grey

-- inspired from Grids, by émilie gillet, one of my favorite modules

-- updated to support crow 2.0.0 ii events

-- crow

  -- in1: 24pqn clock input
  -- in2: reset

  -- out1: bass drum
  -- out2: snare drum / clap
  -- out3: closed high hat
  -- out4: random even clock triggers

-- txi

  -- knob1: kick amount
  -- knob2: snare amount
  -- knob3: hats amount
  -- knob4: pattern

-- Notes: 
  -- Adjust boost to taste (see below). 
  -- Please write your own patterns! The idea is that things left of noon have kicks on the 4's and things the right of noon can go asymetical. I've just done 7 patterns but you can do more, but keep the total odd. 
  -- Patterns 1 and noon are intentionally variations on each other, so conside keeping those as "safe" locations to dial to. 
  -- A stylistic choice was made to have patterns become similar at high values to ease transitions when using the device on it's own.

-- Boost creates a variation in the indicated measure by adding the boost value to whichever knobs you indicate in boostMap
boost = 3 -- value added
boostMeasure = 4 -- measure to add then reset, 2-8 all add interest, 0 disables
boostMap = {true,true,false} -- kick, snare, hats

patterns = {
          -- 0 is off, values are included based on knobs positions, max knob value (16) auto rolls
          {{1,0,14,0, 7,0,14,0, 4,0,9,0, 7,0,14,0},
           {9,0,0,0, 1,0,0,0, 9,0,0,0, 7,0,0,0},
           {9,0,7,0, 14,0,1,0, 9,0,7,0, 14,0,1,0}},
          
          {{1,0,14,0, 7,0,14,0, 4,0,9,0, 7,9,14,0},
           {0,0,7,0, 0,0,1,9, 0,0,7,0, 0,0,4,14},
           {7,0,9,0, 4,0,9,0, 7,0,9,0, 1,0,9,0}},
          
          {{1,0,13,0, 7,0,9,0, 3,0,9,0, 7,0,14,0},
           {14,0,7,0, 11,0,1,0, 14,0,7,0, 11,0,4,0},
           {9,0,11,0, 3,14,7,14, 9,0,11,0, 1,13,7,14}},

          -- noon roller
          {{1,0,14,0, 7,0,14,0, 4,0,9,0, 7,0,14,0},
           {9,0,0,0, 1,0,0,0, 9,0,0,0, 7,0,0,0},
           {14,0,1,0, 9,0,7,0, 14,0,1,0, 9,0,7,0}},
          
          {{1,0,14,9, 0,14,0,7, 14,0,4,0, 14,0,11,11},
           {0,0,0,0, 1,0,0,0, 0,9,0,14, 9,0,7,0},
           {9,14,9,0, 6,14,6,0, 12,14,12,0, 1,14,2,0}},
          
          {{1,0,14,0, 14,0,9,14, 14,0,14,0, 4,0,7,0},
           {0,0,0,0, 0,0,0,0, 1,0,0,9, 0,0,14,14},
           {0,0,7,0, 0,0,1,0, 0,0,7,0,  0,0,1,0}},

          {{1,0,4,0, 9,7,14,0, 1,0,4,0, 9,7,14,0},
           {0,0,0,0, 0,0,1,9, 0,0,0,0, 0,0,7,0},
           {9,0,1,0, 9,0,7,0, 9,0,1,0, 9,0,7,0}}

--        {0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0} blank for new patterns

}

knobMap = { 
  [1] = "kickMask",
  [2] = "snareMask",
  [3] = "hatMask",
  [4] = "pattern"
  
}

knobs = {
  kickMask = 7;
  snareMask = 2;
  hatMask = 4;
  pattern = 1

}

clock = 1
measure = 1


-- Trent's TXi snipet
chan = 1
--

function init()
    input[1]{ mode      = 'change'
            , direction = 'rising'
            }
    input[2]{ mode      = 'change'
            , direction = 'rising'
            }

    for n=1,4 do
        output[n].slew = 0.0015
    end

    -- Poll TXi params to set clock divisions
    metro[1].event = function(c) 
        for i=1,4 do ii.txi.get('param', i) end
    end
    metro[1].time = 0.05
    metro[1]:start()
    
    -- Trent's TXi snipet
    ii.txi.event = function( event, data )
       --print(event.arg)
       if event.name == 'param' then
          chan = event.arg
          if data > 9.98 then data = 9.98 end -- kludge: prevent nil index for peak value              
          if (chan == 4) then
              knobs[knobMap[chan]] = (quantizeToStepsDetent(data,#patterns,1))+1
              -- if chan == 4 then  print(chan, ":", data, "/", (quantizeToSteps(data,15,1.1))+1) end
          else
              knobs[knobMap[chan]] = (quantizeToSteps(data,17,1.1))
          end
          --print(chan)
       end
    end
    --

end

-- clock
input[1].change = function()
    --print('Tick : ',clock)
    theBoost = 0
    for n=1,3 do
        -- BOOST!
        if ((measure == boostMeasure) 
         and (boostMap[n])
         and (knobs[knobMap[n]] > 0))
        then
          theBoost = boost
        else
          theBoost = 0
        end
        --print(knobs[knobMap[n]])
        if (((patterns[knobs.pattern][n][clock] <= (knobs[knobMap[n]] + theBoost)) 
         and (patterns[knobs.pattern][n][clock] > 0)) 
          or (knobs[knobMap[n]] == 16)) --roll baby
            then output[n]( pulse() ) 
        end
    end
    if ((math.random(2) == 1) and ((clock % 2) == 1)) then output[4]( pulse() ) end

    -- print(knobs.pattern)
    -- print(knobs.kickMask)
    -- print(knobs.snareMask)
    -- print(knobs.hatMask)
    if clock == 16 then
        -- print(measure, "/",boostMeasure)
        clock = 0
        if (boostMeasure > 0) then
          if measure == boostMeasure then
            measure = 0
          end  
          measure = measure + 1
        end
    end
    clock = clock + 1
end

-- reset
input[2].change = function(s)
	    --print ('Reset')
      clock = 1
      measure = 1
end

function quantizeToStepsDetent(value, steps, centerWidth)
  local centerBottom = 5 - (centerWidth / 2)
  local centerTop = 5 + (centerWidth / 2)
  --print ( value, centerBottom, centerTop, centerWidth)
  if (value < centerBottom) then
      return math.floor(range(0, centerBottom, 0, math.floor(steps / 2), value))
  elseif (value > centerTop) then
      return math.floor(range(centerTop, 10, math.ceil(steps / 2), steps, value))
  else
      return math.floor(steps / 2)
  end
end

function quantizeToSteps(value,steps)
  return math.floor(range(0, 10, 0, steps, value))
end

function range(min, max, newMin, newMax, value)
    return newMin + (value - min) * (newMax - newMin) / (max - min)
end

function round(value)
 return (value > 0.5) and math.ceil(value) or math.floor(value)
end
Troubleshooting i2c to the TXi:

I was having a hell of a time with my TXi’s and this script, where it would seemingly randomly drop connectivity during use. I spent hours trying to debug this from both the crow and the TXi side with no luck. However! When I made my own i2c cables, the problem disappeared. So try new cables if you’re getting unexplainable behavior.

Suggestions for Expansion and Modification

This idea can go much further; and as noted above, I’ve already moved beyond this. I see this as a useful starting point for people exploring this idea, and if you decide to explore this route, please let me know your ideas! Here are some considerations that I’ve made to spur your imagination:

  • In this implementation, I have the last output doing a random voltage. You could also make this a 4th pattern channel and gang it’s density control with one of the other knobs.
  • You could make the last channel a Turing machine that changes based on one ore more knob positions. It could generate a new pattern only when you change patterns for example.
  • You could add blending back into the patterns and forgo the “notched” input reading that this script currently does to bring it closer to Grids.
  • You could gang snare and hat density in order to re-introduce the X/Y navigation of Grids.
  • I’ve repurposed the TXi inputs in a further evolution of this script, but you could also use them to improve this script! You could add mutes, add complex controls for a 4th channel Turing Machine, jump to a random pattern each measure if high, boost if high, roll a channel when high. You could even mix these effects based on which set of inputs are high. Implement swing or the mimic the chaos knob from Grids! There are lots of possibilities.
  • You could introduce negative numbers to patterns and have the left side of the knob ignore negative values and the right side of the pattern knob use absolute values. This is akin to how the “Grids” mode in Marbles works to weight snare and kick density from it’s root patterns. Which is another way you could approach this concept!

Removing the TXi

Some suggestions for how you might make use of this without a TXi.

If you wanted to use this script WITHOUT a TXi, you could with some big sacrifices: Feed a clock to input one (start a timer when you get a clock, if it expires, reset the global position to 0 - so you can auto reset). You don’t strictly need a reset, I’ve just chosen to because I didn’t need the other input for anything else in my particular usage. If you do this auto-reset trick, you can then read CV on input 2, but you’d only have one global density control. That might work okay for you, depending on how you set your expectations. You would also, accordingly, only have one pattern with how things are setup, but there are ways around that!

You could make going all the way to 0 a kind of pattern advance or randomization and have a “soft 0” above it. You could also try to come up with some kind of gestural control (if CV dips down then up advance the pattern back, if the CV dips up then down, then advance the pattern). But it wouldn’t be smooth, and you’d have to be very thoughtful about how patterns transition.

Alternatively, you could sacrifice density and rely on external muting or manually plugging and unplugging of jack to control what’s on and off. If you did that the CV could act as a pattern morph control. I didn’t elect to to do this in my script. It just depends on your priorities and what you want the script to do.

If you wanted to get super weird with it, you could also control the clock via internal scripting and catch a trigger to perform a manual sync with something like a stack cable as a temporary patched connection. Then you could use both inputs as you saw fit, but you could fall out of sync potentially, so it would depend what your goals were (is this for live or studio use).

22 Likes

Wow, thank you for this thorough contribution! I am on a different path at the moment, but this sounds really interesting and I have a feeling I’ll be trying something like this at some point.

1 Like

A bit of a necro bump here, but I’ve been playing around with this script on an off since it got shared. I really want to thank @grey for sharing it together with all the great info, it’s been a source of inspiration.

I hope it’s ok for me to share a modified version of the script. I was initially curious to see if I could actually use the original patterns from Grids with this script.

I liked the idea of not having 256 steps of sensitivity so I remapped Grids patterns to use 16 values per step. I also had to adjust the script so it supported 32 steps rather than 16. I was a bit lazy and didn’t adapt the clock so you need to feed it quite a fast clock to get it going into a nice speed. Probably will make that a bit better in the future.

The first struggle I had was with the fact that simply editing the script and adding the patterns from Grids would just crash my crow. I realised I was hitting a memory limit.

After trying a few things like using tabs instead of spaces for indenting (saves a few characters), removing spaces that wouldn’t break the script and trying to shorten all function names, I still struggled. So in the end what I did was encode the patterns into hex numbers and added a crude decoding function that I use within the function that reads the pattern.

For a few months I had it setup like that and it was a lot of fun. Then a couple of days ago I realised I wanted to do a bit more and decided to use my faderbank to add a few more interesting ways of modifying patterns. I also thought faders might be easier to deal with than knobs, at least for quick changes.

And so, I present to you a modified version of the grid. I added rotation of the pattern for each track with faders 5 to 7. The rotation didn’t actually give very musical results originally, mainly because simply rotating the pattern to one of the 32 steps would make the whole beat become quite ugly (to me) and it was difficult to do predicable and interesting changes. So I ended up constraining it to 16th notes, which works out to be pretty nice. Constraining to other divisions like quarter notes or eights is also quite interesting.

I will likely add a few more things to hopefully use all 16 faders. Should definitely add back the Boost feature of the original script (which I took out in my attempt to make the file size smaller when Crow was crashing). Different levels of boost per track perhaps. I might also add a fourth track with new patterns? A bit daunting to come up with new patterns for it though.

Here’s the script:

Anyway, hope this is interesting to someone. The Grid was definitely interesting to me so I wanted to share what I did with it.

3 Likes

Neat! I’ve done a lot of analysis of Grids, so I know that conversion was quite an undertaking. I’ll try to make some time to mess with this soon.

A thought: I don’t have a 16n, but instead of rotation (which I think of as most useful for Euclidean patterns), I could imagine having a per measure Boost offset, which could allow for some interesting animation of the pattern.

Yeah, for sure I want to add the Boost offset maybe per track, and maybe some sort of Boost density parameter, that one for sure for each track. What I am looking into doing now is navigation for the drum patterns using the 5 by 5 matrix that Grids uses using two faders, x and y (as seen here but without the bit rotation maybe? ).

On converting the original patterns from Grids, that was tricky for sure. I think to make it work without editing your code much I had to invert the ranges as well? I have some python code I used for that, but by now I’ve forgotten exactly what I did. Anyway, I’m having a lot of fun coming back to playing with this :slight_smile:

1 Like