Instances of functions, with their own names each?

Hey guys…

Since I plan to use Sonic Pi in a rather “traditional” way for the moment (to make covers where I trigger sequences, as opposed to a mainly algorithmic approach), I find myself using lots of functions…

But they all share the same general basic shape…

So I was wondering if it is possible to built generalist functions (Something I’d call like melodies_in_general, or drums_in_general), and make particular instances of those super functions, when I need to add something to my song… And then call those particular instances, instead of their parent function.

But right now, I have no idea how I’d do that, or even if it is possible, since calling a function depends on the name you give it at first…

It’s not about how to give them arguments/parameters… My problem is I don’t how I would name them, to differentiate them from the generalist form.

Any ideas please?

If the way I formulate the question is not clear enough, I’ll be happy to try again…

Cheers!

(PS: My first ever Sonic Pi project is taking longer than I expected, because the song I chose seems to be way less “approachable” than I first thought… It’s a very loopy song, so I thought I’d be easy to replicate… Surprise… It’s not! But I’ll get there eventually, I hope.) :grin:

1 Like

I guess my first question would be what your other programming background might be? It sounds like you are looking for inheritance (which is at the class level not the function level) or composition (which might fit better into the overall paradigm of Sonic Pi).

1 Like

Hey there…

I have zero background in programming (Just bought the famous How To Design Programs book, but I’m still at the very beginning), and even less than zero formal musical training…
So it is very possible that what you might suggest will go way over my head…
I mean, if it doesn’t work for my current level of understanding, I’ll go on on doing what I’ve been doing so far… It works, but I’ll take longer… :slightly_smiling_face:

I can almost see what you’re getting at. I reckon have another go :sweat_smile:
It might help to provide a more detailed/concrete/specific example if you can - more visual maybe or a code (or pseudo-code) example to illustrate your question :slightly_smiling_face: - how it reads currently, without your ‘generalist functions’, then roughly what the intended goal is. Might be easier than just trying to describe it in an abstract way… :man_shrugging:

2 Likes

Sounds like defining your own function, and passing
parameters to it is what you are after…

use_bpm 90

define :hats do |d|
  density d do
    sample :drum_cymbal_closed
    sleep 1
  end
end

live_loop :trapHats do
  hats(ring, 4, 4, 3, 8, 4, 6, 4, 16).tick
end

n = [1, 0, 1, 0, 2, 0, 0, 0, 1, 1, 0, 1, 2, 0, 1, 0]
live_loop :trapBeat do
  n.each.with_index do |i|
    sample :bd_haus if i == 1
    sample :drum_snare_hard if i == 2
    sleep 0.25
  end
end

If not, let us know.

Eli…

2 Likes

Hey guys… Thanks for the responses…
As I said before, I’m very new to programming.
As such, I realize some of my questions might be a bit silly…
I think this was one of those silly questions… Sorry about that.
I have thought about this a little better, and I realized that I just had to turn more elements of the definitions of my functions into parameters… That would allow me to call the function as usual when I need to trigger it, and then “load” the relevant data (variations in melodies or drums) that I need at a given moment, by just changing the values for those parameters during a new call to the same function… Obvious and simple. :unamused:
It seems I forgot that more things can be turned into parameters, any part of the function really…

2 Likes

Coming late to this party, and it would seem from the thread that you’ve solved your problem, but I’ll weigh in anyway, I hope you don’t mind.

There is an oft-quoted saying in computing: There are two hard problems in computing: cache invalidation and naming things (and buffer overruns, and so on…). It is difficult to come up with good names for functions, which are descriptive of what they do and/or how to use them.

I think it takes practice, and also you can learn a few things (good and bad) by reading other people’s code. So here are a couple of examples:

  • 𝛴∏ Knight Rider
    • I named the different musical motifs by number krn (Knight Rider) and kbn for “Knight Bass”.
    • The krn and kbn functions take a parameter which is which note to base the motif on, kitt also has how many times to repeat the phrase, and how loud
    • Also the cue names are named after what musical section or instrument is being cued
  • 𝛴∏ Dr. Pi (Doctor Who)
    • This one uses onomatopoeia to name different sections of the song by roughly what it sounds like
1 Like

Hi! I’m not sure what you are trying to achieve and why having functions with parameters can’t achieve it, but your description “instances of functions” sounds a lot like a concept called “higher order function” which (to simplify it a bit) consists of functions that returns other functions (it can also refer to functions that take other functions as parameters, which is more or less what we do when we use blocks, but here I’ll use it to mean the former, returning a function).

In Ruby we can do that with lambda. The object returned behaves like a function, the only difference is that you can’t call it directly with whatever, whatever(), or whatever(args); you need to explictly use the method whatever.call (although there is a shortcut with whatever[args]).

All this probably sounds blurry, I’ll use an example:

define :my_rhythm do |sample1, sample2|
  lambda do
    at(0, 1, 2, 3, 4) { sample sample1 }
    at(1, 3) { sample sample2 }
    sleep 4
  end
end

elec_rhythm = my_rhythm(:bd_haus, :sn_dub)
acoustic_rhythm = my_rhythm(:drum_kick, :drum_snare)

# now elec_rhythm and acoustic_rhythm are like functions, only that you need the call method

live_loop :drums do
  elec_rhythm.call
end

As I say this doesn’t achieve anything that you can’t achieve passing arguments to normal functions, but maybe it helps clarify naming in some cases.

(I’m sorry I don’t have SonicPi in this computer so I could only verify that this works to some extent, using a normal Ruby; So there might be some tiny detail of sonic-pi specific code (the use of at, the sample names) that might be wrong but the principles hold).

2 Likes

Hi @porras

i get this error

Any idea ?


Runtime Error: [buffer 8, line 5] - ArgumentError

Thread death +--> :live_loop_drums

wrong number of arguments (given 4, expected 0..2)

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2437:in `at'

workspace_eight:5:in `block (4 levels) in __spider_eval'

workspace_eight:17:in `block (3 levels) in __spider_eval'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2196:in `block in live_loop'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2216:in `block (2 levels) in live_loop'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2136:in `block (2 levels) in loop'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2357:in `block_duration'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2394:in `block_slept?'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2135:in `block in loop'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2133:in `loop'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2133:in `loop'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2214:in `block in live_loop'

C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/runtime.rb:1093:in `block (2 levels) in __in_thread'Runtime Error: [buffer 8, line 5] - ArgumentError
Thread death +--> :live_loop_drums
 wrong number of arguments (given 4, expected 0..2)
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2437:in `at'
workspace_eight:5:in `block (4 levels) in __spider_eval'
workspace_eight:17:in `block (3 levels) in __spider_eval'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2196:in `block in live_loop'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2216:in `block (2 levels) in live_loop'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2136:in `block (2 levels) in loop'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2357:in `block_duration'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2394:in `block_slept?'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2135:in `block in loop'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2133:in `loop'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2133:in `loop'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/lang/core.rb:2214:in `block in live_loop'
C:/Program Files/Sonic Pi/app/server/ruby/lib/sonicpi/runtime.rb:1093:in `block (2 levels) in __in_thread'

it seems to be an at syntax question and the samples name was wrong (as you warned us). Hard to guess when you are far from sonicpi :slight_smile: @porras

So this works

# tested on v3.3.1 04/03/2022
#

use_bpm 120

define :my_rhythm do |sample1, sample2|
  lambda do
    at [0, 1, 2, 3] do
      sample sample1
    end
    at [1, 3 ] do
      sample sample2
    end
    
    sleep 4
  end
end

elec_rhythm = my_rhythm(:bd_haus, :sn_dub)
acoustic_rhythm = my_rhythm(:drum_bass_hard, :drum_snare_hard)

# now elec_rhythm and acoustic_rhythm are like functions, only that you need the call method

live_loop :drums do
  
  elec_rhythm.call
end

live_loop :drums_3 do
  acoustic_rhythm.call
end

Edit : it’s an interesting way of creating music

# 
#

use_bpm 120

define :my_rhythm do |sample1, sample2|
  lambda do
    at [0, 1, 2, 2.5, 3] do
      sample sample1
    end
    at [1, 3 ] do
      sample sample2
    end
    
    at [0, 0.25, 0.5, 1, 1.5, 1.75, 2, 2.25, 2.5, 3, 3.5 ] do
      sample :drum_tom_lo_hard, finish:0.75, compress: 1, cutoff:90
    end
    
    
    sleep 4
  end
end

elec_rhythm = my_rhythm(:bd_haus, :sn_dolf)
##| sample :vinyl_backspin

s = [ :vinyl_scratch, :vinyl_rewind, :vinyl_hiss]
# as this you can see clearly which sample you are going to use with its name
# you can change the index to get the sample you want and reload to hear the changes.
acoustic_rhythm = my_rhythm( s[2], :bass_dnb_f)

# now elec_rhythm and acoustic_rhythm are like functions, only that you need the call method

live_loop :drums do
  
  elec_rhythm.call
# you can comment this line as you want :-)
  sleep 4  
end

live_loop :drums_3 do
  acoustic_rhythm.call
end
1 Like