How to implement a tranceGate control pattern in TidalCycles?

I am trying to implement a tranceGate function in TidalCycles that applies a classic trance-style gating effect directly as a ControlPattern, to be used like this:

d1 $ n "0" # s "supersaw" # tranceGate 0.05 "1[11]0"

The function should interpret a string of symbols per cycle, where each symbol represents a step in the gate:

  • 1 means: gradually release the current sound’s gain down to 0 over a linear duration specified by r (in seconds), then immediately return its gain to the level it had before the gate affected it.
  • 0 means: rest for that step, leaving the volume completely untouched — whatever sound is playing continues naturally.

The number and grouping of symbols define subdivision within the cycle. Bracketed groups like "[11]" should be treated as compressed steps within a single slice of the cycle (e.g., "1[11]0" creates a different rhythmic gate density than "1110"). The release time r should remain constant regardless of symbol density, just like a standard envelope release in melodic patterns.

My goal is not random chopping but precise, deterministic gating based on the step sequence. The resulting function should produce rapid rhythmic stutters typical of trance gates, not full-cycle silences or long sustained fades.

I have attempted several implementations, including bracket parsing and envelope construction, but so far the output results in entire-cycle fades or silence instead of step-level gating. Here is my most recent attempt:

let
  tranceGate :: Double -> String -> Pattern Double
  tranceGate r s = cat $ map symbolToPattern (concatMap expandGroup (parseBrackets s))
    where
      symbolToPattern '1' = fast 1 $ cat [pure 1, slow (pure $ toRational r) (pure 0), pure 1]
      symbolToPattern '0' = pure 1
      symbolToPattern _   = pure 1
      parseBrackets [] = []
      parseBrackets ('[':xs) =
        let (grp, rest) = span (/= ']') xs
        in grp : parseBrackets (drop 1 rest)
      parseBrackets (x:xs) = [x] : parseBrackets xs
      expandGroup grp = if length grp > 1
                        then map id grp
                        else grp
in do
  panic
  d1 $ n "0" # s "supersaw" # gain (tranceGate 0.05 "1[11]0")
  d2 $ s "kick:8*4"

I have been stuck on this for days. Every variation either produces full-cycle sounds and rests or removes the sound entirely. How can I correctly implement tranceGate so that it:

  • Interprets symbols per cycle with bracket grouping,

  • Applies step-level gating with linear release (r seconds),

  • Restores volume instantly after each gated step,

  • Works as a proper ControlPattern chained with #

Any guidance or working approach would be greatly appreciated.

gradually release ...

so the gain parameter will go through a sequence of steps. if you don't want to break the structure of the original pattern (re-trigger a long-running sample, a synthesizer with long sustain), you need control busses https://tidalcycles.org/docs/reference/control_busses .
note that gain cannot be bussed, but amp can.

parseBrackets ...

You are re-implementing mini-notation here. Don't. Just write patterns for the argument of ampbus.

Example (with long-running sample) :

let down s = fastcat $ map (\ k -> pure $ fromRational (k % s)) $ reverse [0 .. s]
    d = down 3
    k = pure 1
in  d1 $ (s "led" # end 0.21)
       # ampbus 42 (slow 4 $ cat [ d, fastcat [d, d], k ])
       # lpfbus 43 (slow 3 $ cat [ 4000, 100, 200 ])

(I cannot test with supersaw right now since my synths seem broken. There was a Fedora package sc3-plugins but that's gone? https://bugzilla.redhat.com/show_bug.cgi?id=2154432 Guess I need to build from source )

I am very interested in a programmable trance gate in TidalCycles similar to the effect demonstrated by Switch Angel in this video: https://youtu.be/GWXCCBsOMSg?si=cibHjK_wpuwtg3um

The corresponding Strudel implementation in JavaScript is available here: https://github.com/switchangel/strudel-scripts/blob/main/allscripts.js. While it works effectively, it is entirely random-driven, with gate openings and closings determined by a pseudorandom generator. What I am looking for goes beyond that: a trance gate where it is possible to explicitly control when gates open and close, how long they remain in each state, and optionally apply pseudorandom variation to any of these controls.

A fully programmable trance gate should be part of the TidalCycles standard library. Such a function would be a fundamental tool for rhythmic gating in electronic music and would benefit the entire community.

As someone who recently discovered TidalCycles and without experience in Haskell or JavaScript, I am seeking guidance, examples, or a native Tidal implementation that could achieve this level of control.

1 Like

I’m not clear on what a trancegate is, but from the code it seems to be struct plus randomisation. So maybe if you don’t want the randomisation then you just want struct?

edit ah reading your description I understand this a bit more.. I don’t fully understand Jade’s implementation, but I’ll have a go at it in tidal..

Do you mean something like this?

trancegate :: Pattern Bool -> ControlPattern -> ControlPattern
trancegate s pat = squeezeJoin $ f <$> s
  where f False = pat
        f True = pat # (segment 32 $ ampbus 1 isaw)

d1 $ trancegate "[1 0]*2 [1 0 1]*2" $ note ("<c'major'4 c'minor'5>") # sound "supersaw"

This seems closer to your description.. The envelope is fixed rather than proportional to event duration (although in cycles, not seconds).

let trancegate :: Pattern Time -> Pattern Bool -> ControlPattern -> ControlPattern
    trancegate r s pat = trigZeroJoin $ f <$> s
      where f False = pat
            f True = pat # (segment 32 $ ampbus 1 $ slow r envLR)
in            
  d1 $ trancegate 0.1 (slow 2 "0 [1 1 1]@2 1*6")
   $ note ("<c'major'4 c'minor'5>") # sound "supersaw"

It’s using an effects bus, so if this is used more than once at the same time, ampbus would need to be passed different bus numbers.

1 Like

Thank you very much for your response, I really appreciate your time.

I wanted to clarify more precisely what I’m aiming for with a trance gate in TidalCycles:

  • The default state of the gate is open, so the sound plays continuously.

  • When a specific symbol is encountered (for example 1), the gate should begin to close gradually over a given duration r, with the volume decreasing linearly from the current level to silence.

  • The moment silence is reached, the gate should snap instantly back open, allowing the sound to continue normally.

  • When the other symbol appears (for example 0), it should do nothing, the gate remains open and the sound is passed through unchanged.

The goal is to be able to explicitly program rhythmic closures, such as:
“close the gate twice very quickly on the first beat, then keep it open for the rest of the cycle.”

Do you think such a controllable gate could be implemented in TidalCycles? Any suggestions or direction would be very helpful.

Thanks again for your support and for all your work on Tidal.

Did you listen to the last example? It is quite close to what you want I think.

This is closer still:

let trancegate :: Pattern Int -> Pattern Time -> Pattern Bool -> ControlPattern -> ControlPattern
    trancegate busid r s pat = pat # (trigZeroJoin $ f <$> s)
      where snapback = when (<1) (const $ segment 8 isaw) $ pure 1
            f False = pat # ampbus busid 0.5
            f True = (ampbus busid $ slow r $ snapback)
in
  d1 $ (trancegate 1 0.05 (slow 2 "0 1*8 1*4 1*8")
   $ note ("<a3'minor'5 e4'minor'5 g3'major'5 a3'minor'5>") # sound "supersaw")
     # hpfbus 2 (segment 32 $ range 0 1000 perlin)
     # hpq 0.2
2 Likes

Thank you so much, @yaxu! Your trancegatesolution is by far the best I’ve seen so far. It’s elegant, precise, and captures the step-level gating I was after.

I made a small aesthetic modification so that the trancegatecan be applied via the # operator, which feels more natural in my workflow. Here’s my adapted version:

let trancegate :: Pattern Int -> Pattern Time -> Pattern Bool -> ControlPattern
    trancegate busid r s = trigZeroJoin $ f <$> s
      where snapback = when (<1) (const $ segment 8 isaw) $ pure 1
            f False = ampbus busid 0.6
            f True  = ampbus busid $ slow r snapback
in do
  d1 $ note ("-14") # sound "supersaw"
    # trancegate 1 0.05 ("1011010110011101") # gain 0.8
  d2 $ s "kick:6*4" -- 4/4 reference

panic

Thanks again for your generosity in sharing your knowledge and making TidalCycles such a rich, live-coding ecosystem! :folded_hands:

2 Likes