Coding in the style of Arvo Pärt and... some questions!

Hi everyone. I’m Chris, french musician and new to sonic pi.
The first thing I tried is a generative version of Fur Alina, the famous piece by Arvo Pärt.

I created functions that corresponds to the “cells” of the piece, and by that I mean the little “parts” of two or more notes that shows little by little the principle of tintinnabuli : m-voice + t-voice.

If you don’t know how Pärt writes music, I highly recommend the Paul Hiller book about the composer’s style, it’s really interesting.

Anyway, here is my code. I’m sure it’s full of inelegant errors (and the commentaries are in french) but it’s a work in progress so …

Also, after I post this, I have a question for you people, about the use of functions :wink:

#code arvo pärt
#fonctions tintinnabuli inspirée de fur alina
#version polyphonique play

define :drone do
play :B2, release: 5, amp: rrand(0.50, 1)
play :B0, release: 5, amp: rrand(0.50, 1)
sleep choose([2, 3, 4, 5])
end

define :a do
play :B3, release: 5, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :Fs2, release: 5, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :b do
play choose([:Cs4,:D4]), attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :B2, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :c do
play choose([:E4,:Fs4]), attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :D3, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :d do
play choose([:G4,:A4,:B4]), attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :Fs3, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :e do
play choose([:Cs4,:D4]), attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :B3, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :f do
play choose([:E5,:Fs5]), attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :D4, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :g do
play choose([:G5,:A5,:B5]), attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :Fs4, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :h do
play choose([:Cs6,:D6]), attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :B4, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :flower do
play :Fs5, attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :Cs4, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :bfa do
play :D4, attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :B2, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

define :dfa do
play :B4, attack: choose([0.2,0.5]), release: 3, amp: rrand(0.50, 1)
sleep choose([0, 1])
play :Fs3, attack: choose([0.2,0.5]), release: 4, amp: rrand(0.50, 1)
sleep choose([0.5, 1, 2, 3, 4, 5])
end

#structure

in_thread(name: :arvopart) do
#uncomment to use one or another
#use_play_defaults port: ‘focusrite_usb_play’
#use_play_defaults port: ‘arturia_minibrute_2’

with_fx :reverb, room: 0.9 do

loop do
  
  use_synth :hollow
  drone
  
  14.times do
    choose ([a, b, c, d, e, f, g, h, flower])
  end
  
end

end
end

5 Likes

So, here is my question :

maybe you noticed that

choose ([a, b, c, d, e, f, g, h, flower])

I discovered that sonic pi just plays the functions in that order, but I guess you understand what I tried there ^^
Is there anyway to do it ?
Help is really welcome.
I’ll be posting the new versions here as soon as I can work on it.

Thanks a lot !

Hi @ChrisLTIR
welcome to the forum!
If I understand your question correctly you are wondering why the choose command always produces the same sequence? If yes, that’s by design. As long as the random seed is the same, the result is deterministic.

If you use

use_random_seed 7
d = choose([1, 2, 3, 4, 5, 6])
print(d)

you can influence the sequence of random elements.

Just a note: when posting code, put it inside 3 backticks ```. Then you’ll get color formating.

Hi ChrisLTIR.
Nice code.

Maybe, we could use rrand_i? in some place?

Hi, thanks for the quick answer !

I came to understand how the > choose command does the same sequence everytime, by reading the tutorial (which is really great !), thank you for the advice on the “use_random_seed” I completely forgot about that.

Actually through my tests this idea came : is that even possible to use that “choose” command to go through a list of functions ? Because actually sometime I really am under the impression that sonic pi just read the order in which I put the functions, ignoring the “choose” command… but I only did a few tests so I’m not sure.

But I guess my question is more about that : is it possible to treat functions like that, like if they were basic elements like notes of music ?

Thanks a lot again :slight_smile:

Hey !

Thanks, I’ll look into that !

I see, you’re looking for something like this

aa = ->{a}
bb = ->{b}
cc = ->{c}
dd = ->{d}

live_loop :foo do
  fun = [aa, bb, cc, dd].choose
  fun.call
end

The syntax aa = ->{...} assigns a lambda function to symbol aa which then can be called using aa.call. Within the body {...} you can have executable expressions, such as calling another function a.

Wow, that really looks like the solution I’m looking for !

Thanks a lot, I’ll try it as soon as I can

By the way, is that syntax anywhere talked about in the tutorial that’s within the software ?

That’s pure Ruby stuff, see Ruby Lambdas.

1 Like

Nice ! Going to investigate further.

Thanks !

That’s definitely one way of doing it, sure. :slightly_smiling_face: There are others, such as using Ruby’s method().

One thing to remember is that as usual, any code which is not specifically from Sonic Pi’s language (ie, plain Ruby for example) is not officially supported and may stop working at any time as we make changes.

Any examples of when a lambda would be particularly desirable when using SPI?

They’re already mentioned sneakily as part of the ‘official’ language, but in very specific usage cases: as a way to filter sample packs for example: Sonic Pi - Tutorial :slight_smile:
(There may have been one or two other places, but I can’t quite remember off the top of my head).

1 Like

It has been used also in this post: Self-modifying algorithms. @amiika came up with the idea to have lambda functions as part of the rules system (= hash map).

2 Likes

Hi @Nechoj and @ChrisLTIR

Just to clear matters, although this is generally accurate in terms of lists/rings of existing elements it’s not actually what’s causing the observed behaviour in this case.

What we have here is a case of non-lazy lists being constructed before any randomisation happens.

It’s a tricky thing to wrap your head around, so give it a few tries after I’ve explained - and I hope I can be clear enough.

Consider this list:

[1, 2, 3]

When the code (text) is read by the computer it constructs an equivalent list in memory that can then be used. This is an implicit action that happens automatically at the time the list is encountered by the language runtime. You can think of it as a “step”.

The following code has two steps:

[1, 2, 3].choose

The steps are:

  1. Construct a list containing the numbers 1, 2 and 3.
  2. Choose a random element from that list.

Hopefully this makes some sense so far.

Now, let’s complicate things by adding functions:

define :a do
  1
end

define :b do
  2
end

define :c do
  3
end

[a, b, c]

So, what happens here? Well first, we define 3 functions called a, b and c. Then we do something we’ve seen earlier which is to “construct a list”. What are the elements of the list? Well, actually we don’t know, we just know that they are the results of the functions a, b and c respectively. So the language runtime calls these functions in turn and uses their results to construct the list [1, 2, 3].

It’s this behaviour of having to construct the list which is what is being observed by the original question.

We can make this really obvious:

define :a do
   puts "hello"
   1
end

[a]

When we run this code, we see that "hello" is printed to the log. This is because the function a had to be called when the list is constructed.

So, to bring it all together, let’s look at all the steps in the following code:

define :a do
  1
end

define :b do
  2
end

define :c do
  3
end

[a, b, c].choose
  1. Define functions a, b and c.
  2. Construct the list [a, b, c] which in turn requires us to…
    1. Call function a which returns 1
    2. Call function b which returns 2
    3. Call function c which returns 3
  3. Put our new list [1, 2, 3] into memory
  4. Choose a random element from the list

Notice that we have to call all the functions in order as part of the list construction.

The solution that uses lambdas avoids the need to call the functions because the lambdas are actually values like numbers, so they can be constructed in memory just fine and calling .choose will just return a random lambda. Only when you call the lambda will its code be executed - so you get to choose when that happens explicitly.

I do hope that this helps in some way. :slight_smile:

3 Likes

Thank you very much !

I’ll look into all of it soon, thanks for the reply ! And congrats Sam on that wonderful software.

1 Like

Thanks @ChrisLTIR and welcome to our community - it’s so lovely to have you here :slight_smile:

1 Like

Very precise and clear, thanks!

1 Like

I love this project! It’s a perfect (to my ear) imitation of Pärt’s style, and that combination of synth and effects sounds great. So impressive.

I’ve done a few similar projects that generate “infinite” versions of a familiar piece of music. It’s such a satisfying exercise—for this semi-lapsed music theorist, it scratches that analytical itch while also being fun to listen to and play with. I keep meaning to clean up and post my efforts—would any of you be interested in contributing to an “album” along these lines?

1 Like