Explain the parameterised function

#1

Please, Could you explain the Parameterised function in general and in this code for me?
I’ve spent too much time trying to understand what is going on here in this code but I failed!

[1, 3, 6, 4].each do |d|
(range -3, 3).each do |i|
play_chord (chord_degree d, :c, :major, 3, invert: i)
sleep 0.25
end
end

#2

Hi @AhmadMRagab,

I’d suggest that one of the keys to understanding what’s going on here is the concept of a ‘block’. See http://sonic-pi.net/tutorial#section-5-1 for example.
Briefly: they are a way of acting on multiple lines of code as a group.
Here, any lines of code between a do and its corresponding end are treated as this group of code (block).
Whatever is on the left hand side of the do is what operates on this block.
In this case it is .each. .each is a function that comes from the Ruby language - (it’s not strictly a Sonic Pi function). It is used to loop through a list of items and do something with each item in the list.
As it is looping through the list, the current item is available for the block to operate on, (the variable in between | | at the start of the block - in our case here, stored as d for the outer loop iterating over [1, 2, 3, 4], or i for the inner loop iterating over (range -3, 3) - a list (ring) of numbers from -3 up to (but not including) 3)).
d and i are used while iterating through these loops as parameters passed to the functions on the third line, play_chord and chord_degree.

Is that any clearer? Let us know if you have specific questions that we could perhaps answer for you.

3 Likes
#3

Hi @AhmadMRagab,

just in addition to @ethancrawford’s helpful explanation, once you have an understanding of the logic of the outer and inner loop, for me it is always helpful to make explicit what happens while the loop is running. So you can take

play_chord (chord_degree d, :c, :major, 3, invert: i)

and see when and with what the params d and i will be replaced:

Outer loop run #1 says: d = 1; inner loop runs with 6 different values from -3 to 2 which produces:

  1. play_chord (chord_degree 1, :c, :major, 3, invert: -3)
  2. play_chord (chord_degree 1, :c, :major, 3, invert: -2)
  3. play_chord (chord_degree 1, :c, :major, 3, invert: -1)
  4. play_chord (chord_degree 1, :c, :major, 3, invert: 0)
  5. play_chord (chord_degree 1, :c, :major, 3, invert: 1)
  6. play_chord (chord_degree 1, :c, :major, 3, invert: 2)

Outer loop run #2 says: d = 3; inner loop runs with 6 different values from -3 to 2 which produces:

  1. play_chord (chord_degree 3, :c, :major, 3, invert: -3)
  2. play_chord (chord_degree 3, :c, :major, 3, invert: -2)
  3. play_chord (chord_degree 3, :c, :major, 3, invert: -1)
  4. play_chord (chord_degree 3, :c, :major, 3, invert: 0)
  5. play_chord (chord_degree 3, :c, :major, 3, invert: 1)
  6. play_chord (chord_degree 3, :c, :major, 3, invert: 2)

aso. …

Hope this helps.

PS.: You should however be clear about what chord_degree with the option invert will do, so if you don’t, look it up in the documentation and play around with it a bit.

3 Likes
#4

PS.: You should however be clear about what chord_degree with the option invert will do, so if you don’t, look it up in the documentation and play around with it a bit.

That might be a bit difficult seeing as I noticed not long after writing my reply above that the invert opt is not documented for the chord_degree function in the language reference! :joy: (which I am currently fixing :slight_smile:)

1 Like
#5

Oh, yes.

But help is near (even without @ethancrawford fixing this): Just look up chord_invert which does just the same :wink: .

#6

Indeed it does :smile:

#7

Thanks, @ethancrawford your explanation was clear and helped me to grasp the basic!
This code is from the examples, exactly “Sonic Dreams” by Sam Aaron
in this code How “num” gets its value? and in this code “amp: rrand(0.5, 1.5) * amp_mul” why did Sam multiple the rrand value by the value of amp_mul.
and here too! “release: rrand(0, 5) + 0.5”.

define :ocean do |num, amp_mul=1|
  num.times do
    s = synth [:bnoise, :cnoise, :gnoise].choose, amp: rrand(0.5, 1.5) * amp_mul, attack: rrand(0, 1), sustain: rrand(0, 2), release: rrand(0, 5) + 0.5, cutoff_slide: rrand(0, 5), cutoff: rrand(60, 100), pan: rrand(-1, 1), pan_slide: 1
    control s, pan: rrand(-1, 1), cutoff: rrand(60, 110)
    sleep rrand(0.5, 4)
  end
end
#8

Thanks, @Martin for your valuable explanation!

#9

Hi @AhmadMRagab,

have a look at Sam’s explanation concerning “Formatting” (3 backticks at the beginning and the end of a code block). This will make your code examples much more readable.

1 Like
#10

Thanks @Martin :slight_smile:

#11

Assuming you took this as a snippet from a longer piece of code, you should see elsewhere in the code a line(s) like: ocean N M or ocean N where ‘N’,‘M’ would be numbers or perhaps variable names. Those lines would be invoking the function and passing the values in as the parameters you see.

#12

This is the whole code from the examples, I’m totally lost!

rand-seed-ver 33

Coded by Sam Aaron

Video: https://vimeo.com/110416910

use_debug false
load_samples [:bd_haus, :elec_blip, :ambi_lunar_land]

define :ocean do |num, amp_mul=1|
  num.times do
    s = synth [:bnoise, :cnoise, :gnoise].choose, amp: rrand(0.5, 1.5) * amp_mul, attack: rrand(0, 1), sustain: rrand(0, 2), release: rrand(0, 5) + 0.5, cutoff_slide: rrand(0, 5), cutoff: rrand(60, 100), pan: rrand(-1, 1), pan_slide: 1
    control s, pan: rrand(-1, 1), cutoff: rrand(60, 110)
    sleep rrand(0.5, 4)
  end
end

define :echoes do |num, tonics, co=100, res=0.9, amp=1|
  num.times do
    play chord(tonics.choose, :minor).choose, res: res, cutoff: rrand(co - 20, co + 20), amp: 0.5 * amp, attack: 0, release: rrand(0.5, 1.5), pan: rrand(-0.7, 0.7)
    sleep [0.25, 0.5, 0.5, 0.5, 1, 1].choose
  end
end

define :bd do
  cue :in_relentless_cycles
  16.times do
    sample :bd_haus, amp: 4, cutoff: 100
    sleep 0.5
  end
  cue :winding_everywhichway
  2.times do
    2.times do
      sample :bd_haus, amp: 4, cutoff: 100
      sleep 0.25
    end
    sample :ambi_lunar_land
    sleep 0.25
  end
end

define :drums do |level, b_level=1, rand_cf=false|
  synth :fm, note: :e2, release: 0.1, amp: b_level * 3, cutoff: 130
  co = rand_cf ? rrand(110, 130) : 130
  a  = rand_cf ? rrand(0.3, 0.5) : 0.6
  n  = rand_cf ? :bnoise         : :noise
  synth :noise, release: 0.05, cutoff: co, res: 0.95, amp: a if level > 0
  sample :elec_blip, amp: 2, rate: 2, pan: rrand(-0.8, 0.8) if level > 1
  sleep 1
end

define :synths do |s_name, co, n=:e2|
  use_synth s_name
  use_transpose 0
  use_synth_defaults detune: [12,24].choose, amp: 1, cutoff: co, pulse_width: 0.12, attack: rrand(0.2, 0.5), release: 0.5 ,  mod_phase: 0.25, mod_invert_wave: 1

  play :e1, mod_range: [7, 12].choose, pan: rrand(-1, 1)
  sleep 0.125

  play :e3, mod_range: [7, 12].choose, pan: rrand(-1, 1)
  sleep [0.25, 0.5].choose

  play n, mod_range: 12, pan: rrand(-1, 1)
  sleep 0.5

  play chord(:e2, :minor).choose, mod_range: 12, pan: rrand(-1, 1)
  sleep 0.25
end

define :play_synths do
  with_fx :reverb do |r|
    with_fx :echo, phase: 0.25 do |e|
      synths = [:mod_pulse, :mod_saw, :mod_dsaw, :mod_dsaw, :mod_dsaw, :mod_dsaw]
      cutoffs = [108, 78, 88, 98]
      synth = synths.rotate!.first
      4.times do |t|
        puts shuffle("0" * (30 - t) + ("1" * t)) unless t == 0
        co = cutoffs.rotate!.first + (t * 2)
        7.times do
          n = chord([:e2, :e3, :e4, :e5][t], :minor).choose
          synths(synth, co, n)
        end
        sleep 2
      end
      sleep 1
      cue :within
    end
  end
end

define :binary_celebration do |n=1, st=1|
  in_thread do
    n.times do
      puts (0..30).map{|_| ["0", "1"].choose}.join
      sleep st
    end
  end
end

puts 'Introduction'
puts 'The Curved Ebb of Carpentry'
sleep 2

cue :oceans
at [7, 12], [:crash, :within_oceans] do |m|
  cue m
end

uncomment do
  use_random_seed 1000
  with_bpm 45 do
    with_fx :reverb do
      with_fx(:echo, delay: 0.5, decay: 4) do
        in_thread do
          use_random_seed 2
          ocean 5
          ocean 1, 0.5
          ocean 1, 0.25
        end
        sleep 10
        use_random_seed 1200
        echoes(5, [:b1, :b2, :e1, :e2, :b3, :e3])
        cue :a_distant_object
        echoes(5, [:b1, :e1, :e2, :e3])
        cue :breathes_time
        in_thread do
          echoes(5, [:e1, :e2, :e3])
        end
        use_synth :tb303
        echoes(1, [:e1, :e2, :e3], 60, 0.9, 0.5)
        echoes(1, [:e1, :e2, :e3], 62)
        echoes(1, [:e1, :e2, :e3], 64, 0.97)
        echoes(1, [:e1, :e2, :e3], 66)
        echoes(1, [:e1, :e2, :e3], 68)
        cue :liminality_holds_fast
        echoes(4, [:b1, :e1, :e2, :b3, :e3], 80)
        echoes(1, [:b1, :b2, :e1, :e2, :b3, :e3], 85,  0.98)
        cue :within_reach
        echoes(5, [:e1, :b2], 90)
        cue :as_it_unfolds
        in_thread do
          echoes(5, [:e1], 90)
        end
      end
    end
  end
end


in_thread(name: :bassdrums) do
  use_random_seed 0
  sleep 22
  3.times do
    bd
  end
  sleep 28
  live_loop :bd do
    bd
  end
end

in_thread(name: :drums) do
  use_random_seed 0
  level = -1
  with_fx :echo do |e|
    sleep 2
    drums -1, 0.1
    drums -1, 0.2
    drums -1, 0.4
    drums -1, 0.7
    puts "Part 2"
    puts "Inside the Machine"
    3.times do
      8.times do
        drums level, 0.8
      end
      6.times do
        drums(level)
      end

      sleep 1
      level += 1
    end
    sleep 4
    cue :dreams
    8.times do
      drums 1, 1, true
    end

    10.times do
      m = choose [shuffle(:within_dreams), :within_dreams, :dreams_within]
      cue m
      drums 2, 1, true
    end

    6.times do
      m = choose [shuffle("within") + "_dreams", :within_dreams.shuffle, "dreams_" + shuffle("within")]
      cue m
      drums 2
    end

    live_loop :drums do
      8.times do |i|
        drums 1
      end

      16.times do |i|
        cue " " * rand_i(32)
        at 1 do
          cue "  " * i
        end
        drums 2
      end
    end
  end
end

in_thread name: :synths do
  use_random_seed 0
  sleep 12
  cue :the_flow_of_logic
  play_synths
end

in_thread do
  use_random_seed 0
  sync :within
  puts "Part 3"
  puts "Reality A"
  sleep 12
  use_synth_defaults phase: 0.5, res: 0.5, cutoff: 80, release: 3.3, wave: 1

  2.times do
    [80, 90, 100, 110].each do |cf|
      use_merged_synth_defaults cutoff: cf
      puts "1" * 30
      synth :zawa, note: :e2, phase: 0.25
      synth :zawa, note: :a1
      sleep 3
    end
    4.times do |t|
      binary_celebration(6, 0.5)
      synth :zawa, note: :e2, phase: 0.25, res: rrand(0.8, 0.9), cutoff: [100, 105, 110, 115][t]
      sleep 3
    end
  end

  puts 'Part n'
  puts 'The Observer becomes the Observed'
  # Your turn...
end
#13

You’re welcome :slight_smile:
In the case of define :ocean… etc, you will notice that much further down in the Sonic Dreams example, the ocean function is actually called - there’s lines like:

ocean 5
ocean 1, 0.5
ocean 1, 0.25

That’s where num gets its values from - here, the numbers 5, and 1 that are passed to the ocean calls.
If you’d like a bit more detail, it’s all helpfully discussed in Section 5.5 of the tutorial, (also at http://sonic-pi.net/tutorial#section-5-5).

As to why Sam used * amp_mul - I can only assume that in certain parts of his music, the random value generated for amp needed to be adjusted slightly. By passing a value to ocean for amp_mul, (eg the 0.5 in ocean 1, 0.5) he’s able to use that value to scale the random number slightly.

Why he used + 0.5 - perhaps it was to ensure that even if the random numbers for attack, sustain and release were very close to zero, that the sound was guaranteed to fade out over at least half a beat? Not sure.

(Ha… only just saw that @perpetual_monday already responded… well, that’s what I get for writing a detailed reply :joy: :+1: )

1 Like