Help understanding ticks in sonification

I am trying to make my first sonification (expressing data through sound) with sonic pi, and I’m having a difficult time (I should note that I am also new to ruby, but not to coding).

What I am trying to do is create a sequence for each month, and (eventually) 4 synths for each quarter of the year. The beat will count what month we are in (1 beat for January, 12 for December) and the chords will play for the number of hirees for that month (I simplified it where every one hiree is 10). The chord sequence should repeat for 4 times, as it should play for each month in a quarter. I realize my [unfinished solution] is VERY naive, but I also haven’t figure out why the ticking isn’t working. I thought I could just make rings and tick through each of the arrays, but it isn’t working. For example, the beat section starts at 2, then jumps to 5 then 7. The chord ring didn’t work at all, and using variables (c instead of chords.tick as written below) also seems to give different results. I have spent far too many hours reading the sonic pi tutorial, this forum, and mehackit (great resource) without much luck. I would appreciate anyone’s advice on ways to improve this code!

hirees = [4, 6, 8, 6, 5, 4, 4, 5, 4, 5, 4, 6].ring
percentages = [7, 9, 12, 10, 9, 6, 6, 8, 7, 8, 7, 10].ring
chords = [chord(:c, :minor7), chord(:F, :minor7), (chord :Bb, "7")].ring
synths = [:piano, :pluck].ring
months = (range -0,13)

s = synths.tick
c = chords.tick
h = hirees.tick

puts months.look

use_bpm 200
puts hirees.look

define :month_beat do
  puts months.look
  months.tick.times do
    sample :bd_ada
    sleep 0.75
  end
end

month_beat

use_synth synths.tick
h.times do
  play c,  amp: percentages[0]/2
  sleep 1
end

##| use_synth :piano
##| hirees[0].times do
##|   play chord(:c, :minor7),  amp: percentages[0]/2
##|   sleep 1
##| end

month_beat

use_synth synths.tick

h.times do
  play c, amp: percentages[1]/2
  sleep 1
end
month_beat

Hello @notintune!
This comment, and the others that follow it, might help:

I can’t believe I missed that! Thanks so much for your response. Then theoretically this method could work if I just put all into a loop? Since it is a pattern, I feel like it could be easily generated, but I’m going from very naive (where I wrote out 3 loops) → pretty naive (which didn’t work)

Perhaps, but the 3 loops should be fine :slightly_smiling_face: the point that Sam refers to is that tick increments a counter unique to a live_loop or a thread, not unique per ring/list. (This includes if called outside of a live loop, (such as the very top-level, which is itself run in a hidden thread)).

So the idea is to use multiple named ticks if you need to separately increment things at different rates in a single loop or thread, or you could use a single tick to increment all rings if they can all advance at the same time, and use look where you need the value.

Hope that helps! Feel free to ask if you need further clarification :slightly_smiling_face:

Thanks Ethan! This is very very helpful. It’s starting to make sense, but I don’t think I really grasp it. How do I increment all my arrays with one tick (and do all my arrays need to be rings?)? I really just want it to go through each value once (for a total of 12 loops), except the ones that have less than 12 values - these should just restart from the first index.

Do you have an example ; I m interested.

2 Likes

this is what I mean by using a single tick to increment multiple rings at once: I’ve edited
your example slightly @notintune to show how you could do this :slight_smile:

hirees = [4, 6, 8, 6, 5, 4, 4, 5, 4, 5, 4, 6].ring
percentages = [7, 9, 12, 10, 9, 6, 6, 8, 7, 8, 7, 10].ring
chords = [chord(:c, :minor7), chord(:F, :minor7), (chord :Bb, "7")].ring
synths = [:piano, :pluck].ring
months = (range -0,13)

hirees.length.times do
  tick
  
  s = synths.look
  c = chords.look
  h = hirees.look
  
  puts months.look
  
  use_bpm 200
  puts hirees.look
  
  define :month_beat do
    puts months.look
    months.look.times do
      sample :bd_ada
      sleep 2
    end
  end
  
  month_beat
  
  use_synth synths.look
  h.times do
    play c,  amp: percentages[0]/2
    sleep 1
  end
  
  ##| use_synth :piano
  ##| hirees[0].times do
  ##|   play chord(:c, :minor7),  amp: percentages[0]/2
  ##|   sleep 1
  ##| end
  
  month_beat
  
  use_synth synths.look
  
  h.times do
    play c, amp: percentages[1]/2
    sleep 1
  end
  month_beat
end

The key here is that I start by iterating over something in an ‘overall’ loop of sorts, (hirees in this case - you could just as easily make it a proper live_loop instead of just looping once over a single collection) and then at the top of the ‘loop’ I call tick every cycle around. Then, whenever I need to use this incremented counter value, I just use .look on whatever array or ring I’m cycling through. Since I’m only incrementing the counter once per iteration of the outer loop, everywhere I use look inside it will see the same counter value (and hence all the values used from these arrays or rings will advance at the same time).

Technically, yes, the ring functions only work on rings. However, if you just want to work with array syntax, Sonic Pi converts arrays to rings behind the scenes if you call a ring function on an array :slight_smile: (such as .tick, .look, etc etc - see Tutorial chapter 8.5 for many more: Sonic Pi - Tutorial)

Let me know if that all makes sense! I’m happy to keep helping if needed :slight_smile:

:joy:
That’ll teach me to not double-check before writing :joy:
Yes, Sonic Pi converts arrays to rings if you call .tick or .look on them, but otherwise you need to use [...].ring yourself :slight_smile:

Apologies :joy:

Hey @ethancrawford. I just want to thank you so much for your help & patience! I think it’s starting to make sense. The only thing I still don’t understand is why I can’t have month_beat play initially and then increment with the loop. I thought that this would be solved by putting it above the loop, like you suggested. Then I thought by making two live loops and making them sync would solve things, but this seemed to stray further from the solution.


hirees = [4, 6, 8, 6, 5, 4, 4, 5, 4, 5, 4, 6].ring
percentages = [7, 9, 12, 10, 9, 6, 6, 8, 7, 8, 7, 10].ring
chords = [chord(:c, :minor7), chord(:F, :minor7), (chord :Bb, "7")].ring
synths = [:piano, :piano, :piano, :pluck, :pluck, :pluck].ring
months = (range 0,13)

months.length.times do
  tick
  
  s = synths.look
  c = chords.look
  h = hirees.look
  p = percentages.look
  m = months.look
  
  
  use_bpm 250
  
  live_loop :month_beat do
    m.times do
      sample :bd_ada
      sleep 2
    end
  end
  live_loop :num_hirees do
    sync :month_beat
    use_synth synths.look
    hirees.look.times do
      play_pattern c, amp: p
    end
  end
end

Is the idea that you want a beat sequence to play first, and once it has finished, one chord sequence, and this repeats with the next series of beat and chord sequences?
If that’s it, then here’s a version adapted from your latest example that does this: (using just one live_loop)

use_bpm 250
hirees = [4, 6, 8, 6, 5, 4, 4, 5, 4, 5, 4, 6].ring
percentages = [7, 9, 12, 10, 9, 6, 6, 8, 7, 8, 7, 10].ring
chords = [chord(:c, :minor7), chord(:F, :minor7), (chord :Bb, "7")].ring
synths = [:piano, :piano, :piano, :pluck, :pluck, :pluck].ring
months = (range 1,12, inclusive: true)


live_loop :num_hirees do
  tick
  months.look.times do
    sample :bd_ada
    sleep 2
  end
  use_synth synths.look
  hirees.look.times do
    play_pattern chords.look, amp: percentages.look
  end
end

Do I understand your goal here? :sweat_smile: :crossed_fingers:

1 Like

I’m off for now, but other folks are always welcome to chip in if I’m not back before them :slightly_smiling_face:

YES!!! Ok that makes so much sense. Thanks for your help. I think I understand the tick, look and loops a lot more now. It doesn’t sound very melodic, but I just want to make sure people will be able to hear the data, especially when it is paired with a graph/animation.

My next challenge will be to play around the the percentage variable, as the amp isn’t very discernable, and to add custom synths.

Thanks again @ethancrawford. I hope I don’t have to bug you again too soon. :sweat_smile:

1 Like

So, one thing to note here is that Sonic Pi’s ‘standard’ amp level for synths is usually 0 - 1. Note that the docs for synths say this about amp:

Typically a value between 0 and 1. Higher amplitudes may be used, but won’t make the sound louder, they will just reduce the quality of all the sounds currently being played (due to compression.)

There is a built in signal limiter in the underlying audio-signal chain, there as a safety net to try to save people’s ears. Thus, after a certain point, the signal is limited to try to cap the levels. This may be why using these large numbers for amp: is not producing very distinctive results. The limiter can be turned off if you wish, by using the following command:

set_mixer_control!(limiter_bypass: 1)

(But of course be aware of the implications if you do :slight_smile:)

You’re welcome! and that’s no problem at all. I’m always happy to help where I can.
If you feel like sharing as the project progresses, by all means, go ahead! It’s always nice to see Sonic Pi projects develop :smile:

1 Like