Random Pattern Generation

Hi,

I have been thinking about random pattern generation since quite a while (and also did some coding :wink: ). SP does provide a whole bunch of options. I especially favor the (active) use of random_seed because it introduces a way to let SP create patterns, decide what should stay and be used (let’s say for a current session) or what you would like to discard.

The moment I began to realize the potential of this feature (= let SP invent on the fly and use these random generations in a controlled/repeatable way) was a Patreon video session Sam gave on the topic ‘randomness in SP’ a while ago.

To start a discussion about that I created a little sketch involving a function which sort of generically makes use of this idea as well as a small ambient example to demonstrate a possible use case:

# Pattern Generator

# Generate a random pattern based on proveded seed
# @param ptn_len integer: number of pattern items
# @param pool list: notes; time, amp or other values
# @seed seed integer: random seed value
# @param downbeat list: leave downbeat empty or e. g. [4, :r]
#                       fill in 1st and every 4th item with pause
define :generate_ptn do | ptn_len, pool, seed, downbeat=nil |
  ptn = []
  use_random_seed seed
  ptn_len.times do | i |
    if downbeat
      if i % downbeat[0] != 0
        ptn.push(pool.choose)
      else
        ptn.push(downbeat[1])
      end
    else
      ptn.push(pool.choose)
    end
  end
  return ptn
end

live_loop :clock do
  sleep 4
end

live_loop :kick, sync: :clock do
  sample :bd_ada
  sleep 1
end

live_loop :ambi, sync: :clock do
  amp = generate_ptn(16, [0, 0.25, 0.5, 0.75, 1], 27988, [2, 0])
  cursor = generate_ptn(16, [0.5, 0.75, 0.25], 18898)
  with_fx :reverb do
    sample :guit_harmonics, amp: amp.tick, start: cursor.look, finish: cursor.look * -1
  end
  sleep 0.25
end

live_loop :background, sync: :clock do
  with_fx :reverb, room: 1 do
    sample :guit_e_fifths, amp: 0.25, attack: 2, release: [2, 4].choose, rpitch: [0.25, 0, 0, -0.25].choose
  end
  sleep 2
end

The following example explores this idea to generate a syncopating kick drum and join some found patterns in order to get a longer sequence (ticking through a ring of seeds):

# Pattern Generator

# Generate a random pattern based on proveded seed
# @param ptn_len integer: number of pattern items
# @param pool list: notes; time, amp or other values
# @seed seed integer: random seed value
# @param downbeat list: leave downbeat empty or e. g. [4, :r]
#                       fill in 1st and every 4th item with pause
define :generate_ptn do | ptn_len, pool, seed, downbeat=nil |
  ptn = []
  use_random_seed seed
  ptn_len.times do | i |
    if downbeat
      if i % downbeat[0] != 0
        ptn.push(pool.choose)
      else
        ptn.push(downbeat[1])
      end
    else
      ptn.push(pool.choose)
    end
  end
  return ptn
end

use_bpm 120

live_loop :clock do
  sleep 4
end

live_loop :kick, sync: :clock do
  #stop
  sample :bd_ada
  sleep 1
end

live_loop :syncopating_kick, sync: :clock do
  amp = generate_ptn(16, [0, 0, 0, 0, 0.125, 0.25], (ring 1, 2, 345, 289).tick, [4, 0])
  16.times do
    sample :bd_ada, amp: amp.tick(:kick)
    sleep 0.25
  end
end

What are your ideas about that?

I have thought that
ONLY PERHAPS ?
Live Loops are a poor choice for randomization with SPi
because each loop starts with the same number giving
a sort of " sameness " between loops that hinders the
??? … Only Perhaps … It is hard to pin down that
type of thing … One might get an " impression " of
so & so , but ???

Thanks for spotting that!

You can skip the bit with the finish value; it’s bullshit what I wrote; could probably better be finish: 1 - cursor.look to prevent that both start and finish end up having the same values all the time…

But it is not essential for the example.

use_bpm 960

pattern=[:r,:r,:r,:r,:r,:r,:r,:r]
drums=[48,60,72,:r,:r,:r]
ride=[2,0,1,1,4,0,1,1]
main=[1,0,1,0,1,0,1,0]

loop do
  for count in 0..7
    
    play 84,amp: ride[count]/32.0

    r=rand_i(3)
    if r==2 then pattern[count]=choose(drums); end
    play pattern[count], amp: main[count]

    sleep 2
  end
end
1 Like

It can indeed sometimes be challenging to find the right sort of code which gives a balance between ‘random-ness’ and ‘repetition’. Even though Sonic Pi’s version of ‘random’ is still deterministic, I think it’s still possible to use it in ways that approach this :smiley:
For myself, I sometimes find it easy to become bored with a percussion loop that I might be building, if it repeats the same way for too long. I might vary it a bit by having 3 bars repeat the same pattern, then have a different fourth bar, and then change the random seed and start again :slight_smile:
(The point being, that we can change the random seed whenever we like, to jump to a different point in the random number stream - we don’t have to pick a seed and then stick with it :grin:)

define :rand_beats do
  16.times do
    sample :loop_amen, onset: pick
    sleep 0.125
  end
end

live_loop :random_test do
  use_random_seed (3000 + tick(:r, step: 10))
  3.times do
    rand_back(16)
    rand_beats
  end
  rand_beats
end
1 Like

@ethancrawford

My point ( now proved invalid ) was that …

  data=[0,1,2,3]

live_loop :A do
  for i in 0..7
    puts choose(data)
end; end

live_loop :B do
  for i in 0..7
    puts choose(data)
end; end

Each ’ live loop ’ would produce the same set
of numbers …

Fair enough! guess my interpretation of ‘each loop’ went a bit loopy :joy:

Hey, thanks, these are both very nice examples - but I still have to take a deeper look especially at @hitsware’s example, which goes a somewhat elegant but abstract way.

EDIT2: Actually very clear code. Thanks again.

My initial example gravitates towards a slightly different idea: It is not about ongoing generative and inventive code but more I would like to use SP’s randomness as a ‘proposal system’. Once I like the proposed idea I can use it exactly because it is deterministic (and put the respective seed in a ring to create an initially random but then ratified and repeated musical phrase … until new seed are being tried out).

I wonder if we could somehow loop back the “proposal” and save the result - rather than just keeping the random seed. Otherwise we are stuck with the structure and can’t evolve the “proposal” as any change to use of randomness (“oh, let me add a note here for accent”) would have to carefully avoid disrupting the random stream. Possible of course, but awkward.

I haven’t tried it yet (maybe you did - I seem to recall something) but it would be neat to “record” the sample/synth/control events that were generated during a live loop and loop them back into “non-random” but “modifiable” code.

2 Likes

Store the " sample/synth/control " generations in an array ( s ) ?

Absolutely @siimphh. Recording streams of events generated by the system and using them to “save” performances and also communicate across instances of Sonic Pi has long been part of the design. It’s just not yet been fully realised.

Interestingly I have been thinking about exactly this part of the system the last few days and I’ve had some new ideas which could open up a lot of opportunity in this space. I’m working on experimental prototypes now to see if my thoughts were indeed feasible :slight_smile:

3 Likes