Calling a function with argument from another function

Hello,
I am a new user of sonic Pi and I’d like to use one of the examples functions: ocean inside the function choose. I have an error with the argument value.

Could you tell me how the call to choose must be done instead of this one:
play choose([60, 65, 72, foo, ocean(3,0.125)])

Thank you.
@nathalierun

Hi @nathalierun,

not sure about how to call :ocean (my wild guess is that this could be done using lambda). As far as I see calling :foo won’t work this way because it already includes a call to play.

So that wasn’t really an answer to your question, was it :wink: ?

An easier way could be to include the random note generation of :ocean into your loop.

Hi Carrié
welcome to Sonic Pi.
The problem is that the play command expects a numeric note or a note symbol.
When it gets a choice of either :foo or :ocean it tries to convert it to a note symbol and fails, producing the error message that you get, as the two functions are not notes. A solution I came up with is shown below.

define :foo do
  play 30
  sleep 1
  play 55
  sleep 0.5
end

define :ocean do |num =3,amp_mul=0.125|
  num.times do
    s = synth [:bnoise, :cnoise, :gnoise].choose, amp: rrand(0.4, 1.5) * amp_mul
    control s, pan: rrand(-1,1),cutoff: rrand(60,110)
    sleep rrand(0.5,4)
  end
end


loop do
  choice=rrand_i(1,4) #get a random integer 1-4
  puts choice
  case choice # use Ruby case statement to process
  when 3 #choice is 3
    foo ##do foo
  when 4 #choice is 4
    ocean # do ocean
  else # otherwise it will be a note
    play [60,65,72][choice-1] #use choice-1 as an index into note list (index starts from 0 not 1)
  end #end the case statement
  sleep 1
end

This chooses a random number in the range 1 to 4 then uses the Ruby case statement to decide its course of action.
It deals with the two functions first. If choice is 3 it calls the function :foo
If choice if 4 it calls the function :ocean
otherwise it uses the value in choice to index which note to play from the list

Two further points. I notice you are using Sonic Pi 2.11.1 which is quite an old version. Not sure what computer you are using, but there are probably later versions available for it.

Secondly you may find it easier to copy and post your code directly into in_thread putting a line of three back ticks before it and a line of three back ticks after it as I have done in this post.

1 Like

Thanks @Martin, I am quite sure that this is the write way, but I don’t know how to use the lambda function.
I’ll try to go further this way.

I haven’t even thought about it. I try this right now, it’s simple.

But I still don’t know how to call a function that has the result of another function as an argument.

Thank you @robin.newman for this very clear and long answer. But your example doesn’t work for me. I get this error:

Oh, I really have the 3.1 version of Sonic Pi (it was an odl example).

Hi @nathalierun, and welcome :slight_smile:
On the highlighted line in that screenshot, you’ll notice that choice is 4 (which is intended to be a comment) is actually being interpreted as some kind of command to run. Change it by putting a missing # in, so that it says when 4 #choice is 4 and it should run happily :slight_smile:

Re using a function call as a parameter to another function: it always depends on what type of thing the inner function (ie foo or ocean in this case) returns, and what the function you’re passing it into as a parameter (here, ultimately play) expects there. In your original example, you can absolutely pass a function into choose, but in that case, if choose picks foo or ocean, they do not return a number or note symbol, and play can’t handle it, as Robin says.

Hopefully with the when 4 line focused in your screenshot above fixed, Robin’s example works fine!

1 Like

Oh ! I see ;-), Thank you. It works now.

So, how do I do a function that calls recursively herself if this function has arguments ???

It’s a rather contrived example, but a way to use lambdas to do roughly what you were after in your original post might be the following:

define :f1 do |note|
  play note
  sleep 1
end

define :f2 do |note|
  play note
  sleep 0.5
end

live_loop :test1 do
  function_in_a_lambda = [-> { f2(:c5) }, -> { f1(:a3) } ].choose
  function_in_a_lambda.call
  
  ##| or another use of lambdas, to easily vary their parameters:
  ##| function_in_a_lambda = [-> (x) { f2(x) }, -> (x) { f1(x) }].choose
  ##| function_in_a_lambda.call([:c5, :a3].choose)
end

(A reminder that things such as lambdas come from the Ruby language directly - they are not true Sonic Pi commands, so while they might work at the moment, they’re not officially supported, and they might stop working if Sonic Pi changes enough).

Hopefully that gives you an idea. Does this example get close to what you’re thinking about?

Sorry I missed the # it was meant to be a comment, which I added in in-thread not in SP so I missed it. I subsequently edited the code

It’s exactly what I was looking for.
I tried this with the :ocean function:

function_in_a_lambda = [-> (x) { f2(x) }, -> (x) { f1(x) }, ->  (x,y) { ocean(x,y) } ].choose
  function_in_a_lambda.call([:c5, :a3, (3, 0.125)].choose)

But I get an error:

I don’t know which syntax I have to use to pass 2 arguments to the :ocean function.

Sure. In this particular case, there’s no way of knowing beforehand whether the parameters that are chosen match the lambda that they are passed to. We’d probably need to do one of several things: either manually identify somehow the selected lambda after you choose one, to determine what kind of parameters to send it: eg:

define :f1 do |note, note2|
  play note
  sleep 1
  play note2
  sleep 1
end

define :f2 do |note|
  play note
  sleep 0.5
end

live_loop :test1 do
  lambdas = [-> (x) { f2(x) }, -> (x, y) { f1(x, y) } ]
  selected = rand_i(lambdas.length)
  if selected == 0
    lambdas[selected].call(:c5)
  elsif selected == 1
    lambdas[selected].call(:a3, :c5)
  end
end

Or, just avoid separating the parameter values outside of the lambda definitions altogether and either send them exactly when the lambdas are defined: eg [-> { f2(:c5) }, -> { f1(:a3, :c5) } ].choose, or leave the determination of those values until you are actually inside the functions f1, f2 themselves… :man_shrugging:

Thanks for your answer. I did this to try your example:

load_samples [:bd_haus, :elec_blip, :ambi_lunar_land]

define :f1 do |note|
  play note
  sleep 1
end

define :f2 do |note|
  play note
  sleep 0.5
end

define :ocean do |num=4, amp_mul=0.2|
  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

live_loop :test1 do
  f_lambdas = [-> (x) { f2(x) }, -> (x) { f1(x) }, -> (x,y) { ocean(x,y) } ]
  selected = rand_i(f_lambdas.length)
  if selected == 0
    f_lambdas[selected].call([:c5, :a3])
  elsif selected == 1
    f_lambdas[selected].call(3, 0.125)
  end
end

And I get this run time error:


I really need to learn how to pass argument to a function randomly choosen (I try to create a tree-fractal music).

Ah. That one is because there are 3 items in f_lambdas, so selected can be either 0, 1, or 2. In which case, the code that decides what to do with it ends up skipping over the lambdas for 0 and 1, and because there is nothing to handle 2, the code immediately tries to loop back around and start again, but the thread has not had a chance to ‘sleep’, meaning that you get the zero time loop error. So in this case, it’s close - you just need to make that last condition selected == 2, and put in something extra to also handle the middle lambda.

Oh ! so sorry !
It works now ! Thank you so much.
Here is the correct code for the live loop. Now, I’ll be able to create somethings that works… :blush:

live_loop :test1 do
  f_lambdas = [-> (x) { f2(x) }, -> (x) { f1(x) }, -> (x,y) { ocean(x,y) } ]
  selected = rand_i(f_lambdas.length)
  if selected == 0
    f_lambdas[selected].call(:c5)
  elsif selected == 1
    f_lambdas[selected].call(:g3)
  elsif selected == 2
    f_lambdas[selected].call(3, 0.125)
  end
end
1 Like

No need to apologise! :slight_smile: You are welcome. Just keep in mind that like I mentioned earlier, lambdas are not officially supported Sonic Pi commands, so may not always work going into the future.
Even so - looking forward to seeing/hearing what you create, if you’re happy to share later :grin:

Of course !
About this, can I take some code bits provided in the examples for my fractal tree music?
What about the license? Is the examples code distributed under a creative commons license? This would allow me to use parts of it.

And if I quote the authors of the pieces of code, is that enough? For instance, :ocean was coded by Sam Aaron, it’s a part of Sonic Dreams.

The music is intended for a scientific video explaining fractals trees for a competition of the Computer Society of France. If the video is selected, it will be licensed under Creative Commons.

I’ll leave the exact confirmation to @samaaron perhaps - but as far as I know, according to https://github.com/samaaron/sonic-pi/blob/master/LICENSE.md#docs-tutorial-and-examples the examples are CC-BY-SA (details of which are linked there) so as long as you abide by the relevant terms, that sounds to me like it’s fine. I’m sure one of us would be able to clarify things if you had any further questions :slightly_smiling_face:

Maybe something like this?

define :abc do
  print 'abc'
  play 80
end


define :doPlay do |d|
  if d.is_a? Integer
    play d
  else
    d
  end
end

loop do
  doPlay [abc, 80, 90, 50, 60].choose
  sleep 0.5
end

Excuse me for the code, I’ve started studying it yesterday.

1 Like

So I should be able to use part of them, if I share the product using the same conditions. Nice. Thank you.

1 Like