File: 05.6-Variables.md

package info (click to toggle)
sonic-pi 3.2.2~repack-8
  • links: PTS, VCS
  • area: main
  • in suites: bookworm
  • size: 71,872 kB
  • sloc: ruby: 30,548; cpp: 8,490; sh: 957; ansic: 461; erlang: 360; lisp: 141; makefile: 44
file content (191 lines) | stat: -rw-r--r-- 5,668 bytes parent folder | download | duplicates (4)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
5.6 Variables

# Variables

A useful thing to do in your code is to create names for things. Sonic
Pi makes this very easy: you write the name you wish to use, an equal
sign (`=`), then the thing you want to remember:

```
sample_name = :loop_amen
```

Here, we've 'remembered' the symbol `:loop_amen` in the variable
`sample_name`. We can now use `sample_name` everywhere we might have
used `:loop_amen`. For example:

```
sample_name = :loop_amen
sample sample_name
```

There are three main reasons for using variables in Sonic Pi:
communicating meaning, managing repetition and capturing the results
of things.

## Communicating Meaning

When you write code it's easy to just think you're telling the computer
how to do stuff - as long as the computer understands it's OK. However,
it's important to remember that it's not just the computer that reads
the code. Other people may read it too and try to understand what's going
on. Also, you're likely to read your own code in the future and try to
understand what's going on. Although it might seem obvious to you now -
it might not be so obvious to others or even your future self!

One way to help others understand what your code is doing is to write
comments (as we saw in a previous section). Another is to use meaningful
variable names. Look at this code:

```
sleep 1.7533
```

Why does it use the number `1.7533`? Where did this number come from?
What does it mean? However, look at this code:

```
loop_amen_duration = 1.7533
sleep loop_amen_duration
```

Now, it's much clearer what `1.7533` means: it's the duration of the
sample `:loop_amen`! Of course, you might say why not simply write:

```
sleep sample_duration(:loop_amen)
```

Which, of course, is a very nice way of communicating the intent of the
code.

## Managing Repetition

Often you see a lot of repetition in your code and when you want to
change things, you have to change it in a lot of places. Take a look at
this code:

```
sample :loop_amen
sleep sample_duration(:loop_amen)
sample :loop_amen, rate: 0.5
sleep sample_duration(:loop_amen, rate: 0.5)
sample :loop_amen
sleep sample_duration(:loop_amen)
```

We're doing a lot of things with `:loop_amen`! What if we wanted to
hear what it sounded like with another loop sample such as
`:loop_garzul`? We'd have to find and replace all `:loop_amen`s with
`:loop_garzul`. That might be fine if you have lots of time - but what
if you're performing on stage? Sometimes you don't have the luxury of
time - especially if you want to keep people dancing.

What if you'd written your code like this:

```
sample_name = :loop_amen
sample sample_name
sleep sample_duration(sample_name)
sample sample_name, rate: 0.5
sleep sample_duration(sample_name, rate: 0.5)
sample sample_name
sleep sample_duration(sample_name)
```

Now, that does exactly the same as above (try it). It also gives us
the ability to just change one line `sample_name = :loop_amen` to
`sample_name = :loop_garzul` and we change it in many places through
the magic of variables.

## Capturing Results

Finally, a good motivation for using variables is to capture the results
of things. For example, you may wish to do things with the duration of a
sample:

```
sd = sample_duration(:loop_amen)
```

We can now use `sd` anywhere we need the duration of the `:loop_amen`
sample.

Perhaps more importantly, a variable allows us to capture the result
of a call to `play` or `sample`:

```
s = play 50, release: 8
```

Now we have caught and remembered `s` as a variable, which allows us
to control the synth as it is running:

```
s = play 50, release: 8
sleep 2
control s, note: 62
```

We'll look into controlling synths in more detail in a later section.


## Warning: Variables and Threads

Whilst variables are great for giving things names and capturing the
results of things, it is important to know that they should typically
only be used locally within a thread. For example, *don't do this*:

```
a = (ring 6, 5, 4, 3, 2, 1)

live_loop :shuffled do
  a = a.shuffle
  sleep 0.5
end

live_loop :sorted do
  a = a.sort
  sleep 0.5
  puts "sorted: ", a
end
```

In the above example we assign a ring of numbers to a variable `a` and
then used it within two separate `live_loop`s. In the first live loop
every `0.5`s we sort the ring (to `(ring 1, 2, 3, 4, 5, 6)`) and then
print it out to the log. If you run the code, you'll find that the
printed list *is not always sorted!*. This may surprise you - especially
that sometimes the list is printed as sorted, and sometimes it is
not. This is called non-deterministic behaviour and is the result of a
rather nasty problem called a race-condition. The problem is due to the
fact that the second live loop is also manipulating the list (in this
case shuffling it) and by the time the list is printed, sometimes it has
just been sorted and sometimes it has just been shuffled. Both live
loops are racing to do something different to the same variable and
every time round a different loop 'wins'.

There are two solutions to this. Firstly, *don't use the same variable
in multiple live loops or threads*. For example, the following code will
always print a sorted list as each live loop has its own separate
variable:

```
live_loop :shuffled do
  a = (ring 6, 5, 4, 3, 2, 1)
  a = a.shuffle
  sleep 0.5
end

live_loop :sorted do
  a = (ring 6, 5, 4, 3, 2, 1)
  a = a.sort
  sleep 0.5
  puts "sorted: ", a
end
```

However, sometimes we do want to share things across threads. For
example, the current key, BPM, synth etc. In these cases, the solution
is to use Sonic Pi's special thread-safe state system via the fns `get`
and `set`. This is discussed later on in section 10.