Shifting Rings from Spread

Hello Folks

I’ve been using spread a lot to generate euclidean rhythms, and they work really well. One thing I can’t seem to figure out is how to shift them. For instance, spread(2,16) gives me a hit on 1 and 8, which sounds off when I try to add snares from one measure to the next. Is there an easy way to “pre-tick” it 4 times, so i get a hit on 5 and 13 instead?

aka. from
(ring true, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false)
to
(ring false, false, false, false, true, false, false, false, false, false, false, false, true, false, false, false)

Hi @chris.krakou,

lovely question! You can skip or “rotate” a spread an arbitrary number of times using the .rotate modifier:

(spread 2,16).rotate(4)  

You can use positive or negative values for the rotate amount.

I hope that this helps :slight_smile:

1 Like

Hi @samaaron,

according to my tests

v = 1
spread(2, 8, rotate: v)

will always return:

(ring true, false, false, false, true, false, false, false)

no matter what value v has. At least in my SP 3.1.0

which is consitant with the documentation:

“… rotate to the next strong beat allowing for easy permutations of the original rhythmic grouping …”.

I would be glad if I am wrong because I was already looking for an easy way to shift beats irrespective of them being week or strong.

Hi @Martin,

yes, that would make sense. The option rotate: is different from the modifier .rotate. In other words, the option rotate: is specific to the spread function whereas the modifier .rotate works on any ring.

Note the difference:

(spread 2, 8, rotate: 4)
(spread 2, 8).rotate(4)

Hope that this helps :slight_smile:

1 Like

Ahh!! Great! Thanks for clearing up my misunderstanding and misreading.

1 Like

That’s exactly what i needed. Thanks @samaaron!

The .rotate function doesn’t appear anywhere in the built-in documentation. Finding the functions built into rings (beyond the tutorial) is generally a bit difficult, it would be nice if all the functions were listed in the ring entry.

I could write a bit about it if you want. Just give a hint as to where I should put it.

1 Like

Hi Chris,

I think .rotate is really a Ruby construct, and as such not documented in the SP help…

There are others, .mirror, .reverse, which come to mind, which were mentioned in another
much older post, somewhere.

Sam usually says they aren’t formally supported, even though they might work, and may not
work in future versions… which would be a shame, as they are damn useful.

Eli…

… and here: https://github.com/samaaron/sonic-pi/blob/master/etc/doc/tutorial/08.5-Ring-Chains.md

What he said. :slight_smile:

I sat down and went through the ruby array operators to check which ones work and which ones don’t as of today (version 3.1.0), in case someone else comes across this thread.

###########
# WORKS
###########

# Transformations
puts (scale :a1, :minor) << 88
puts (scale :a1, :minor).unshift(33)
puts (scale :a1, :minor).push(33)
puts (scale :a1, :minor).pop
puts (scale :a1, :minor).shift
puts (scale :a1, :minor).delete_at(1)
puts (scale :a1, :minor).reverse

# Combining
puts (scale :c1,:major) + (scale :c2,:major)
puts (scale :c1,:major) + (scale :d1,:major).uniq
puts (scale :c1,:major) - (scale :d1,:major)

# Removing Duplicates
puts (scale :c1,:major).concat((scale :d1, :major))

# Moving
puts (scale :c1,:major).reverse
puts (scale :c1,:major).rotate
puts (scale :c1,:major).rotate(-2)

# Querying
puts (scale :c1,:major).include? 29
puts (scale :c1,:major).count 29
puts (scale :c1,:major).size

# Iterations
(scale :c1, :major).each {|i| puts i}
(scale :c1, :major).reverse_each {|i| puts i}
(scale :c1, :major).each_with_index do |note,index|
  puts "#{note} at #{index} "
end

###########
# DOESN'T WORK
###########

# boolean operators
puts testring & (scale :d1,:minor)
puts testring | (scale :d1,:minor)

# Joining
puts (ring "hello","world").join
2 Likes

Hi everyone

going to revive this thread, as I am also interested in Euclid generated patterns.
I have the VPME.DE Circles and it has a mode where it also outputs the inverse. I had wondered how to do this with Sonic Pi and realised that it was a question of evaluating whether a ring item was true or false. I’m sure there are more refined ways to achieve this but, generally pleased with the result, especially the variable rotate. The inverse (false) would be good to deploy for alternating between open/closed hats.

live_loop :euclidPatt do
  ticky = tick
  
  if one_in(6)
    rot = rrand_i(0,2)
  else
    rot = 0
  end
  
  t = (spread 3,8, rotate:rot)
  #Positive
  sample :perc_snap, release: 0.2, pan: -1, amp: 0.3 if (t[ticky] == true)
  #Inverse of the Eucild pattern
  sample :elec_flip, rate: 2, release: 0.2, pan: 1, amp: 0.4 if (t[ticky] == false)
  sleep 0.25
end

Cheers
Hussein

Hi @Hussein,

I might rewrite your code as the following:

live_loop :euclidPatt do  
  tick
  if one_in(6)
    rot = rrand_i(0,2)
  else
    rot = 0
  end
  
  t = (spread 3,8, rotate:rot)
  #Positive
  sample :perc_snap, release: 0.2, pan: -1, amp: 0.3, on: t.look
  #Inverse of the Eucild pattern
  sample :elec_flip, rate: 2, release: 0.2, pan: 1, amp: 0.4, on: !t.look
  sleep 0.25
end

Note, that I just call tick (there’s no need to assign the result as you can use .look to “look it up” later). I then use the on: opt instead of if as this lets you put the option anywhere in the options and not just at the end. It also supports counting 1 as true and 0 as false which can be useful in other situations. Finally I used the ! modifier which returns the logical inversion of the value (!true == false).

I hope that this helps :slight_smile:

Hi @samaaron cheers for the adaptation.

I sometimes run into issues using the !, i.e. not flipping when I expect it to.

In respect of on: is this preferred rather than using if? I’ve wondered about additional opts after the if statement, so this is good to know.

Much appreciated.

I’d love to see an example of this behaviour.

In respect of on: is this preferred rather than using if? I’ve wondered about additional opts after the if statement, so this is good to know.

I certainly prefer it (and designed it) for the reasons I stated above :slight_smile:

yes, I’d love to make it happen consistently. I’m not saying it’s SPi, just getting odd results, that I need to better understand what might be causing them.

I was wondering about on because I picked up the if(spread... from the help docs. So if I read this right, on is good to use where the ring is in a variable result from running spread, rather than inline after amp: using an if statement. is that correct or gone down the wrong rabbit hole?

Cheers.

Essentially I’d use on: instead of a trailing if in all cases - unless I know specifically that I need an if.

The main semantic difference (which is rarely significant) is that the a false value after a trailing if causes none of the statement preceding the if to be executed. It’s as if that line wasn’t part of the code. However, with the on: opt, the line is executed, just that the synth or MIDI note isn’t triggered if the value is false, nil or 0 (note that if treats 0 as a truthy value).

So unless an explicit need for an if, always use on.
Thanks for that, I’ll move in this general direction.

1 Like

Also reviving this thread as I’m getting syntax errors from the code @chris.krakou (in version 3.3.1).

# Not working
# Transformations
##| puts (scale :a1, :minor) << 88
##| puts (scale :a1, :minor).unshift(33)
##| puts (scale :a1, :minor).push(33)
##| puts (scale :a1, :minor).pop
##| puts (scale :a1, :minor).shift
##| puts (scale :a1, :minor).delete_at(1)

# Removing Duplicates
##| puts (scale :c1,:major).concat((scale :d1, :major))

# Querying
##| puts (scale :c1,:major).include? 29
##| puts (scale :c1,:major).count 29

# Iterations
##| (scale :c1, :major).reverse_each {|i| puts i}

No idea what’s changed since 3.1.0… and I’m “too lazy” to check now.

The change is that scales are now treated as rings. You need to convert to lists and remove extraneous bits at start. To be fair you are using Ruby constructs like << .push .pop and these are not guaranteed to work.

The solution looks like this:

# Not working
# Transformations
puts (scale :a1, :minor).to_a.flatten << 88

puts (scale :a1, :minor).to_a.flatten.unshift(33)
puts (scale :a1, :minor).to_a.flatten.push(33)
puts (scale :a1, :minor).to_a.flatten.pop
puts (scale :a1, :minor).to_a.flatten.shift
puts (scale :a1, :minor).to_a.flatten.delete_at(1)

# Removing Duplicates
puts (scale :c1,:major).to_a.flatten.concat((scale :d1, :major).to_a.flatten)

# Querying
puts (scale :c1,:major).to_a.flatten.include? 29
puts (scale :c1,:major).to_a.flatten.count 29

# Iterations
(scale :c1, :major).to_a.flatten.reverse_each {|i| puts i}

The addition of .to_a.flatten appropriately sorts it out.

puts scale :a1,:minor)   => (ring <SonicPi::Scale :A :minor [33, 35, 36, 38, 40, 41, 43, 45])
puts scale( :a1, :minor).to_a  => #<SonicPi::Scale :A :minor [33, 35, 36, 38, 40, 41, 43, 45]>
puts scale:a1, :minor).to_a.flatten.  => [33, 35, 36, 38, 40, 41, 43, 45]

EDIT you can add .ring at the end if you want a ring as the final result.
I’m not sure why the format of scale includes SonicPi::Scale, .flatten by itself removes this
@samaaron might like to comment

1 Like

Thanks!
Also helping me grasp a few more features of rings.