# Tips on simplifying/optimizing code?

**URL:** <https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913>\
**Category:** Creations & Ideas\
**Created:** [June 4, 2024, 8:58am UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913 "2024-06-04T08:58:12Z")\
**Posts on this page:** 15\
**Page:** 1

<div class="post-metadata">

**Author:** ![zir0](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/zir0/32/2096_2.png) [@zir0](https://in-thread.sonic-pi.net/u/zir0)\
**Post date:** [June 4, 2024, 8:58am UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/1 "2024-06-04T08:58:12Z")

</div>

Hey folks, I’ve been working on creating generative music compositions in Sonic Pi with the hopes of creating a generative “album” of “tracks” that play indefinitely. I’ve been using Sonic Pi to generate MIDI data, that is then paired up with a DAW (in my case Ableton) to synthesize the sound. You can find an example of one of these compositions [here](https://www.youtube.com/watch?v=FLkjhWONfWw).

Now that I have the basic process down (tutorials coming soon!) I’ve started making more complex compositions. Right now, for instance I’m working on a track that has five instruments with different degrees of variability, MIDI CCs that act as LFOs, and this thing I’m calling a “phrase controller.” In most linear music, you’ll have different sections of a track that play in a particular order (intro, bridge, hook, etc), but with generative music, you don’t want the track to necessarily play the same sections over and over again in the same order, so I’m using this phrase controller to determine what parts are playing and with what variables at any given time.

I think the ideas behind what I’m creating are really interesting, and I’m really digging the sounds. However, its become obvious that the code is majorly overly complex and bloated. This has become particularly apparent given that when in Phrase 3, drum beats have started skipping and jumping. I’d love any tips and tricks you have on simplifying and optimizing the code!

```ruby
# LUPIN TULPA

/
Beginning code:
- set BPM of track
- create :tick to sync the rest of the live_loops
/
use_bpm 88

live_loop :tick do
  sleep 1
end

/
Define instruments:
- use parameters in function that can be controlled by "phrase_controller"
- set midi channels
/

define :choir do
  use_midi_defaults channel: 1
  sus = (knit 4, 2, 8, 1, 16, 1).choose
  ##| sus = 1
  
  midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus*3
  sleep sus
  midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus*2
  sleep sus
  midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus
  sleep sus
end

define :bass do |sus|
  
  use_midi_defaults channel: 2
  bass_note = [:e1, :g1, :a1, :g1].tick
  
  midi bass_note, sustain: sus
  sleep sus
  midi bass_note, sustain: sus
  sleep sus
  midi :es1, sustain: sus
  sleep sus
  midi bass_note, sustain: sus
  sleep sus
end

define :guit do |prob1, prob2, prob3, speed|
  sus = (knit 0.25, 2, 0.5, 1).choose
  ##| sus = 1
  
  if one_in(prob1)
    midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus*speed, channel: 3
    sleep sus*speed
    midi (scale :e4, :major, num_octaves: 1).choose, sustain: sus*speed, channel: 3
    sleep sus*speed
    midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus*speed, channel: 3
    sleep sus*speed
    midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus*speed, channel: 3
    sleep sus*speed
  end
  
  if one_in(prob2)
    midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus*speed, channel: 4
    sleep sus*speed
    midi (scale :e4, :major, num_octaves: 1).choose, sustain: sus*speed, channel: 4
    sleep sus*speed
    midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus*speed, channel: 4
    sleep sus*speed
    midi (scale :e3, :major, num_octaves: 1).choose, sustain: sus*speed, channel: 4
    sleep sus*speed
  end
  
  if one_in(prob3)
    midi (scale :e3, :major, num_octaves: 2).choose, sustain: sus*speed, channel: 3
    midi (scale :e3, :major, num_octaves: 2).choose, sustain: sus*speed, channel: 4
    sleep sus*2*speed
    midi (scale :e4, :major, num_octaves: 2).choose, sustain: sus*speed, channel: 3
    midi (scale :e4, :major, num_octaves: 2).choose, sustain: sus*speed, channel: 4
    sleep sus*2*speed
    midi (scale :e3, :major, num_octaves: 2).choose, sustain: sus*speed, channel: 3
    midi (scale :e3, :major, num_octaves: 2).choose, sustain: sus*speed, channel: 4
    sleep sus*2*speed
    midi (scale :e3, :major, num_octaves: 2).choose, sustain: sus*speed, channel: 3
    midi (scale :e3, :major, num_octaves: 2).choose, sustain: sus*speed, channel: 4
    sleep sus*2*speed
  end
  
  sleep 0.25
end

define :drum1 do
  use_midi_defaults channel: 5, sustain: 0.25
  midi :g2
  sleep 2
  midi :g2
  sleep 0.25
  midi :f2
  sleep 1.75
end

define :drum2 do
  use_midi_defaults channel: 5, sustain: 0.25
  midi :g2
  sleep 0.5
  midi :d2
  sleep 1.5
  midi :g2
  sleep 0.25
  midi :f2
  sleep 1.75
end

define :drum3 do
  use_midi_defaults channel: 5, sustain: 0.25
  midi :g2
  sleep 0.5
  midi :d2
  sleep 1.5
  midi :g2
  sleep 0.25
  midi :f2
  sleep 0.5
  midi :gs2
  sleep 0.25
  midi :a2
  sleep 0.25
  midi :d2
  sleep 0.75
end

define :drum4 do
  use_midi_defaults channel: 5, sustain: 0.25
  midi :g2
  sleep 0.5
  midi :d2
  sleep 0.5
  midi :gs2
  sleep 1
  midi :g2
  sleep 0.25
  midi :f2
  sleep 0.5
  midi :gs2
  sleep 0.25
  midi :a2
  sleep 0.25
  midi :d2
  sleep 0.75
  
  midi :g2
  sleep 0.5
  midi :d2
  sleep 0.75
  midi :e2
  sleep 0.75
  midi :g2
  sleep 0.25
  midi :f2
  sleep 0.5
  midi :gs2
  sleep 0.25
  midi :a2
  sleep 0.25
  midi :d2
  sleep 0.25
  midi :b2
  sleep 0.5
end

define :drum5 do
  use_midi_defaults channel: 5, sustain: 0.25
  midi :g2
  sleep 0.5
  midi :d2
  sleep 0.5
  
  midi :gs2
  sleep 0.25
  midi :e2
  sleep 0.5
  midi :b1
  sleep 0.25
  
  midi :g2
  sleep 0.25
  midi :f2
  sleep 0.25
  midi :cs3
  sleep 0.25
  midi :gs2
  sleep 0.25
  
  midi :a2
  sleep 0.25
  midi :d2
  midi :gs2
  sleep 0.75
  
  midi :g2
  sleep 0.5
  midi :d2
  sleep 0.5
  
  midi :gs2
  sleep 0.25
  midi :e2
  sleep 0.5
  midi :b2
  sleep 0.25
  
  midi :g2
  sleep 0.25
  midi :f2
  sleep 0.5
  midi :gs2
  sleep 0.25
  
  midi :a2
  sleep 0.25
  midi :d2
  sleep 0.25
  midi :b2
  sleep 0.5
end

define :drum6 do
  use_midi_defaults channel: 5, sustain: 0.25
  #1
  midi :g2
  sleep 0.5
  midi :d2
  sleep 0.5
  
  #2
  midi :gs2
  sleep 0.25
  midi :e2
  sleep 0.5
  midi :b1
  sleep 0.25
  
  #3
  midi :g2
  sleep 0.25
  midi :f2
  sleep 0.25
  midi :cs3
  sleep 0.25
  midi :gs2
  sleep 0.25
  
  #4
  midi :a2
  sleep 0.25
  midi :d2
  midi :gs2
  sleep 0.25
  midi :b1
  sleep 0.5
  
  #5
  midi :g2
  sleep 0.5
  midi :d2
  sleep 0.5
  
  #6
  midi :gs2
  midi :as2
  sleep 0.25
  midi :e2
  sleep 0.5
  midi :b2
  sleep 0.25
  
  #7
  midi :g2
  sleep 0.25
  midi :f2
  sleep 0.25
  midi :cs3
  sleep 0.25
  midi :gs2
  sleep 0.25
  
  #8
  midi :a2
  midi :d2
  sleep 0.25
  midi :gs2
  midi :d2
  sleep 0.25
  midi :b2
  midi :d2
  sleep 0.5
end

live_loop :cc_tuner do
  stop
  cc10 = (line 70, 50, steps: 100).mirror.tick
  midi_cc 20, cc10, channel: 1
  sleep 1
end

live_loop :cc_control, sync: :tick do
  ##| stop
  
  cc3 = (line 10, 30, steps: 40).mirror.tick
  midi_cc 13, cc3, channel: 1
  
  cc4 = (line 0, 127, steps: 200).mirror.tick
  midi_cc 14, cc4, channel: 1
  
  cc5 = (line 127, 0, steps: 200).mirror.tick
  midi_cc 15, cc5, channel: 1
  
  cc6 = (line 50, 70, steps: 100).mirror.tick
  midi_cc 16, cc6, channel: 1
  
  cc7 = (line 70, 50, steps: 100).mirror.tick
  midi_cc 17, cc7, channel: 1
  
  sleep 1
end

/
Phrase controller:
- Select which phrase will play
- Determine how many times a phrase will play before selecting next phrase
- Create live_loops that use phrase variable to determine the changes in the 
/
live_loop :phrase_control, sync: :tick do
  ##| stop
  
  phrase = 3
  repeats = 12
  
  if phrase == -3 then
    sleep 1
  end
  if phrase == -2 then
    sleep 1
  end
  if phrase == -1 then
    sleep 1
  end
  if phrase == 0 then
    ##| repeats = 4
    cc1 = (line 50, 90, steps: 400).mirror.tick
    midi_cc 11, cc1, channel: 1
    
    cc2 = (line 90, 70, steps: 400).mirror.tick
    midi_cc 12, cc2, channel: 1
    
    cc8 = (line 30, 60, steps: 100).mirror.tick
    midi_cc 18, cc8, channel: 1
    
    cc9 = (line 60, 90, steps: 100).mirror.tick
    midi_cc 19, cc9, channel: 1
    
    cc10 = (line 50, 100, steps: 200).mirror.tick
    midi_cc 20, cc10, channel: 1
    
    sleep 1
  end
  if phrase == 1 then
    ##| repeats = 4
    cc1 = (line 50, 70, steps: 400).mirror.tick
    midi_cc 11, cc1, channel: 1
    
    cc2 = (line 90, 70, steps: 400).mirror.tick
    midi_cc 12, cc2, channel: 1
    
    cc8 = (line 40, 80, steps: 50).mirror.tick
    midi_cc 18, cc8, channel: 1
    
    cc9 = (line 40, 70, steps: 100).mirror.tick
    midi_cc 19, cc9, channel: 1
    
    cc10 = (line 50, 80, steps: 200).mirror.tick
    midi_cc 20, cc10, channel: 1
    
    sleep 1
  end
  if phrase == 2 then
    ##| repeats = 4
    /
    cc1 = (line 30, 50, steps: 400).mirror.tick
    midi_cc 11, cc1, channel: 1

    cc2 = (line 90, 70, steps: 400).mirror.tick
    midi_cc 12, cc2, channel: 1
    /
    cc8 = (line 80, 100, steps: 50).mirror.tick
    midi_cc 18, cc8, channel: 1
    
    cc9 = (line 40, 90, steps: 100).mirror.tick
    midi_cc 19, cc9, channel: 1
    /
    cc10 = (line 50, 80, steps: 200).mirror.tick
    midi_cc 20, cc10, channel: 1/
    
    sleep 1
  end
  if phrase == 3 then
    ##| repeats = 4
    sleep 1
  end
  
  
  
  repeats.times do
    ##| stop
    
    live_loop :choir_phrase, sync: :tick do
      if phrase == -3 then
        choir
      end
      if phrase == -2 then
        choir
      end
      if phrase == -1 then
        choir
      end
      if phrase == 0
        choir
      end
      if phrase == 1 then
        choir
      end
      if phrase == 2 then
        choir
      end
      if phrase == 3 then
        choir
      end
    end
    
    live_loop :bass_phrase, sync: :tick do
      if phrase == -3 then
        bass 4
      end
      if phrase == -2 then
        bass 4
      end
      if phrase == -1 then
        bass 4
      end
      if phrase == 0
        bass 2
      end
      if phrase == 1 then
        bass 4
      end
      if phrase == 2 then
        bass 4
      end
      if phrase == 3 then
        bass 4
      end
    end
    
    live_loop :guit_phrase, sync: :tick do
      if phrase == -3 then
        guit 2, 2, 1, (knit 1, 2, 2, 1).choose
      end
      if phrase == -2 then
        guit 1, 1, 4, 2
        if one_in(2)
          sleep (knit 4, 1, 8, 2, 16, 1).choose
        end
      end
      if phrase == -1 then
        guit 4, 0, 0, (knit 4, 1, 8, 4).choose
        if one_in(1)
          sleep (knit 4, 1, 8, 2, 16, 4).choose
        end
      end
      if phrase == 0
        sleep 1
      end
      if phrase == 1 then
        sleep 1
      end
      if phrase == 2 then
        sleep 1
      end
      if phrase == 3 then
        sleep 1
      end
    end
    
    live_loop :drum_phrase do
      if phrase == -3 then
        sleep 1
      end
      if phrase == -2 then
        sleep 1
      end
      if phrase == -1 then
        sleep 1
      end
      if phrase == 0 then
        drum1
      end
      if phrase == 1 then
        drum2
      end
      if phrase == 2 then
        drum3
        ##| drum4
      end
      if phrase == 3 then
        drum5
        drum6
      end
    end
    
  end
  
  if phrase == -3 then
    phrase = (knit -3, 4, -2, 1).choose
  end
  if phrase == -2 then
    phrase = (knit -3, 1, -2, 4, -1, 1).choose
  end
  if phrase == -1 then
    phrase = (knit -2, 1, -1, 4, 0, 1).choose
  end
  if phrase == 0 then
    phrase = (knit -1, 1, 0, 4, 1, 1).choose
  end
  if phrase == 1 then
    phrase = (knit 0, 1, 1, 4, 2, 1).choose
  end
  if phrase == 2 then
    phrase = (knit 1, 1, 2, 4, 3, 1).choose
  end
  if phrase == 3 then
    phrase = (knit 2, 1, 3, 4).choose
  end
  
  print "phrase", phrase
  ##| sleep 1
end

```

I’ve also noticed that the phrase changes invariably after every tick rather than sticking to one phrase according to the “repeats” variable. I figure this is because of the placement of the following code, but I could be wrong.

```ruby
if phrase == -3 then
    phrase = (knit -3, 4, -2, 1).choose
  end
  if phrase == -2 then
    phrase = (knit -3, 1, -2, 4, -1, 1).choose
  end
  if phrase == -1 then
    phrase = (knit -2, 1, -1, 4, 0, 1).choose
  end
  if phrase == 0 then
    phrase = (knit -1, 1, 0, 4, 1, 1).choose
  end
  if phrase == 1 then
    phrase = (knit 0, 1, 1, 4, 2, 1).choose
  end
  if phrase == 2 then
    phrase = (knit 1, 1, 2, 4, 3, 1).choose
  end
  if phrase == 3 then
    phrase = (knit 2, 1, 3, 4).choose
  end

```

---

<div class="post-metadata">

**Author:** ![pashultz](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/pashultz/32/1964_2.png) [@pashultz](https://in-thread.sonic-pi.net/u/pashultz)\
**Post date:** [June 11, 2024, 11:58pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/2 "2024-06-11T23:58:41Z")

</div>

Hi! I love your description of the project. I haven’t tried running your code yet, as I’m just slightly too lazy to rig up a MIDI synth to play it on, but I can suggest a few idioms to shorten/simplify the code. (Apologies if you’ve already figured this out—it’s been more than a week since you posted this. I really should start checking in more often!)

1. When the same value is used over and over, set it in a variable. For example in the `:guit` block there are a lot of lines like

2. Instead of using a sequence of `if` statements to choose the current phrase, try putting them in an array. So instead of:

3. Some of the patterns are written with long sequences alternating between `midi` and `sleep` statements. There are a lot of ways to simplify these. My personal go-to is to use two rings, one for pitches and one for durations. So instead of:

4. In phrases 0, 1, and 2 I suspect the MIDI controllers are changing faster than you intend. Note that the `.tick` method increments the value for the entire thread, so this code actually advances each `line` five steps:

Whew, sorry that came out so long! I hope some of it is helpful. 😄 Best of luck!! And if you ever have a chance to get an audio recording, I’d love to hear how it sounds. [edit: never mind, I should have clicked the link in your post! Listening now.]

---

<div class="post-metadata">

**Author:** ![zir0](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/zir0/32/2096_2.png) [@zir0](https://in-thread.sonic-pi.net/u/zir0)\
**Post date:** [June 12, 2024, 11:14am UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/3 "2024-06-12T11:14:00Z")

</div>

Hi @pashultz. Thank you so much for your comprehensive follow up. All of these tips are incredibly helpful! I’m reading through and it all makes a lot of sense. I’m going to implement these (both in the Lupin Tulpa track I’d posted here, as well as some of my other compositions) and report back.

1. This makes a ton of sense. Disappointed I didn’t think of that 😛
2. I’m not sure that I fully understand this.  
First, I don’t really understand how “it’s possible for several of these `if` blocks to be triggered on a single pass” (though I do understand that this could lead to the complication I’m experiencing here and in other tracks). My intention was to: check what phrase is currently assigned, run specific code based on what phrase is assigned, and then select another phrase based on the current phrase (making it possible to only stay within the same phrase or move up or down by one phrase). Could you help me understand how the current set up is allowing several `if` blocks to trigger on a single pass?  
Second, I don’t think I fully understand the alternate code you’ve offered, but let me try to explain it back to you and perhaps you can point out if/where I’m mistaken. Here, I’m defining an array with potential options, ordered by phrase (but they are not explicitly linked to a specific “phrase” variable, so in a sense where as before phrase == -3 can generate (knit -3, 4, -2, 1).choose, now that becomes phrase\_sequence [0]). Then phrase is set at the end of the block by passing the current phrase into the array `phrase_sequence` and “running” choose. I think I understand the logic a bit more typing it out (sometimes that’s what it takes 😛) but I’m still concerned about one thing in particular. In this case, wouldn’t the phrase variable be remapped from -3 → 3 to 0 → 6? Not the biggest problem, I can rewrite the code to match this, but want to double check.
3. Thank you for this! I’ve seen this structure around in-thread but never fully understood it, I’ll definitely implement it for more of the randomized melodies, though I’m tempted to keep the more precise compositions, like drum patterns, arranged in this way so that it’s easier to human-read timings.
4. 🤯 This is huge, thank you for this! I had no idea, and this has been causing me some frustration in another track. This might be a basic question, but when you say check the Lang reference, can you explain what you mean? I’m currently using VS Code to use Sonic Pi, but I can still open up the default editor if need be.

Thanks again for all your help, I really appreciate it. I haven’t made any recordings of this specific track yet, but can post/send your way as I do more documentation. [This is a more recent generative composition](https://www.youtube.com/watch?v=aV2SEpOzgK4&t=75s) (a bit of ambient meets noise) where I’m also controlling Hydra Video Synth with the MIDI CCs coming out of Sonic Pi. The CC ticking thing in point 4 was causing a lot of issues here, so this is really helpful. The internet connection was pretty whack so the visuals drop a lot of frames, but I think the music is pretty consistent throughout (with a lot of intentional glitching).

---

<div class="post-metadata">

**Author:** ![pashultz](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/pashultz/32/1964_2.png) [@pashultz](https://in-thread.sonic-pi.net/u/pashultz)\
**Post date:** [June 13, 2024, 6:24am UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/4 "2024-06-13T06:24:36Z")

</div>

Ah, I’m so glad some of that was helpful! I’m definitely not an expert, but I’ve had to learn a few lessons the hard way (like the `tick`/`look` behavior, and a specific thing about syncing `live_loop`s—iykyk) so at this point they are especially salient to me. 😄

Thanks for that YouTube link. I love it! I’ve never used Ableton (just Ardour on Linux), and these synths/textures are so rich and interesting. You’ve got a way with Hydra too; those scanline effects are 🧑‍🍳 🔥

You guessed it: by “Lang reference” I meant the last tab of the built-in documentation browser in the Sonic Pi editor. The built-in documentation is super handy: you can just press ctrl-i (on Windows and Linux; not sure what it is on macOS) to jump to the reference for the thing at the cursor. I’m an Emacs diehard in the rest of my life, but for Sonic Pi this feature is compelling enough to keep me in the default editor. (Plus I never really figured out how to control it from Emacs 😆 )

You’re absolutely right about my rewrite of the `phrase` thing! I should have adjusted the numbers in the `knit` structures to match the indices in the array, or maybe more simply changed `phrase_sequence[phrase]` to `phrase_sequence[phrase + 3]`. Sorry for the confusion! As for how it could trigger multiple `if` blocks: it checks every condition every time. If `phrase` is -3, it might get set to -2. Then it might get set to -1 immediately, then 0, then 1, all before it loops back and actually executes the code for phrase 1. That doesn’t seem like the expected behavior. To avoid this, you could use [the `elsif` statement](https://www.rubyguides.com/ruby-tutorial/ruby-if-else/) or perhaps better [the case statement](https://www.rubyguides.com/2015/10/ruby-case/) from Ruby (the language on which Sonic Pi is built). The array approach feels pretty natural to me, but they should all work the same.

Anyway, now I’m looking back at this, I think you were right that it has more to do with the way the code is arranged: I haven’t really tried nesting `live_loop`s inside each other, and I’m having a hard time wrapping my head around what it does. My approach would be to put all the `live_loop`s at the top level, and use the get/set mechanism to pass the value of `phrase` among all the loops. Sort of like this:

```ruby
live_loop :phrase_control do
  # use the get/set syntax to update the phrase variable. I admit this is ugly!
  set :phrase, phrase_sequence[get[:phrase]].choose
  # this loop should fire only once per phrase. If you know the duration, you can sleep:
  sleep phrase_length
end
  
live_loop :choir_phrase do
  sync :tick
  # get the phrase from the global state
  if get[:phrase] == -3 then #etc.
end

live_loop :guit_phrase do
  sync :tick
  if get[:phrase] == -3 then #etc.
end

```

Does that make sense? This get/set business is one of the less intuitive parts of SP, syntax-wise. At first I always wanted to put parentheses after `get`, as if it were a function call. But once I got my head around it, it turned out to be awfully useful.

---

<div class="post-metadata">

**Author:** ![zir0](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/zir0/32/2096_2.png) [@zir0](https://in-thread.sonic-pi.net/u/zir0)\
**Post date:** [June 14, 2024, 3:50pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/5 "2024-06-14T15:50:46Z")

</div>

> Thanks for that YouTube link. I love it! I’ve never used Ableton (just Ardour on Linux), and these synths/textures are so rich and interesting. You’ve got a way with Hydra too; those scanline effects are 🧑‍🍳 🔥

I really appreciate the kind words! A lot of the synths that I use (especially for generative compositions) are using Surge XT, an open-source synth/VST. I’d definitely recommend it, and they have a Linux build.

> As for how it could trigger multiple `if` blocks: it checks every condition every time. If `phrase` is -3, it might get set to -2. Then it might get set to -1 immediately, then 0, then 1, all before it loops back and actually executes the code for phrase 1. That doesn’t seem like the expected behavior. To avoid this, you could use [the `elsif` statement](https://www.rubyguides.com/ruby-tutorial/ruby-if-else/) or perhaps better [the case statement](https://www.rubyguides.com/2015/10/ruby-case/) from Ruby (the language on which Sonic Pi is built). The array approach feels pretty natural to me, but they should all work the same.

Great thanks, I’ll definitely take a look at these and report back.

> Anyway, now I’m looking back at this, I think you were right that it has more to do with the way the code is arranged: I haven’t really tried nesting `live_loop` s inside each other, and I’m having a hard time wrapping my head around what it does. My approach would be to put all the `live_loop` s at the top level, and use the get/set mechanism to pass the value of `phrase` among all the loops.

Yeah, I’m definitely not wedded to the nest `live_loop`s, and I actually just “discovered” get/set for another track\* so I’ll give it a shot here. I think I’d just nested them in order to ensure that `phrase` could be set once and affect all of the nested loops.

\*_side note: I feel weird calling these “tracks” do we have another word to describe our coded music? Particularly when it’s generative and not confined to a set length of time?_

---

<div class="post-metadata">

**Author:** ![zir0](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/zir0/32/2096_2.png) [@zir0](https://in-thread.sonic-pi.net/u/zir0)\
**Post date:** [June 14, 2024, 5:20pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/6 "2024-06-14T17:20:34Z")

</div>

@pashultz  
Okay, I’ve gone ahead and implemented your changes. Thank you again for all of your suggestions! I actually went ahead and even implemented the `rings()` because I figured I won’t edit the pattern much more at this point. This is the new and improved code! (with a few questions to follow):

```ruby
# LUPIN TULPA

/
Beginning code:
- set BPM of track
- create :tick to sync the rest of the live_loops
/

use_bpm 88

live_loop :tick do
  sleep 1
end

/
Define instruments:
- use parameters in function that can be controlled by "phrase_controller"
- set midi channels
/

define :choir do
  use_midi_defaults channel: 1
  sus = (knit 4, 2, 8, 1, 16, 1).choose
  choir_scale = (scale :e3, :major, num_octaves: 1)
  
  midi choir_scale.choose, sustain: sus*3
  sleep sus
  midi choir_scale.choose, sustain: sus*2
  sleep sus
  midi choir_scale.choose, sustain: sus
  sleep sus
end

define :bass do |sus|
  
  use_midi_defaults channel: 2
  bass_note = [:e1, :g1, :a1, :g1].tick
  
  midi bass_note, sustain: sus
  sleep sus
  midi bass_note, sustain: sus
  sleep sus
  midi :es1, sustain: sus
  sleep sus
  midi bass_note, sustain: sus
  sleep sus
end

define :guit do |prob1, prob2, prob3, speed|
  sus = (knit 0.25, 2, 0.5, 1).choose
  guit_scale3_1 = (scale :e3, :major, num_octaves: 1)
  guit_scale4_1 = (scale :e4, :major, num_octaves: 1)
  guit_scale3_2 = (scale :e3, :major, num_octaves: 2)
  guit_scale4_2 = (scale :e4, :major, num_octaves: 2)
  ##| sus = 1
  
  if one_in(prob1)
    midi guit_scale3_1.choose, sustain: sus*speed, channel: 3
    sleep sus*speed
    midi guit_scale3_1.choose, sustain: sus*speed, channel: 3
    sleep sus*speed
    midi guit_scale3_1.choose, sustain: sus*speed, channel: 3
    sleep sus*speed
    midi guit_scale3_1.choose, sustain: sus*speed, channel: 3
    sleep sus*speed
  end
  
  if one_in(prob2)
    midi guit_scale3_1.choose, sustain: sus*speed, channel: 4
    sleep sus*speed
    midi guit_scale4_1.choose, sustain: sus*speed, channel: 4
    sleep sus*speed
    midi guit_scale3_1.choose, sustain: sus*speed, channel: 4
    sleep sus*speed
    midi guit_scale3_1.choose, sustain: sus*speed, channel: 4
    sleep sus*speed
  end
  
  if one_in(prob3)
    midi guit_scale3_2.choose, sustain: sus*speed, channel: 3
    midi guit_scale3_2.choose, sustain: sus*speed, channel: 4
    sleep sus*2*speed
    midi guit_scale4_2.choose, sustain: sus*speed, channel: 3
    midi guit_scale4_2.choose, sustain: sus*speed, channel: 4
    sleep sus*2*speed
    midi guit_scale3_2.choose, sustain: sus*speed, channel: 3
    midi guit_scale3_2.choose, sustain: sus*speed, channel: 4
    sleep sus*2*speed
    midi guit_scale3_2.choose, sustain: sus*speed, channel: 3
    midi guit_scale3_2.choose, sustain: sus*speed, channel: 4
    sleep sus*2*speed
  end
  
  sleep 0.25
end

define :drum1 do
  use_midi_defaults channel: 5, sustain: 0.25

  notes = ring(
    :g2, :g2, :f2
  )

  duration = ring(
    2, 0.25, 1.75
  )

  3.times do
    tick
    midi notes.look
    sleep duration.look
  end
end

define :drum2 do
  use_midi_defaults channel: 5, sustain: 0.25

  notes = ring(
    :g2, :d2, :g2, :f2
  )

  duration = ring(
    0.5, 1.5, 0.25, 1.75
  )

  4.times do
    tick
    midi notes.look
    sleep duration.look
  end
end

define :drum3 do
  use_midi_defaults channel: 5, sustain: 0.25

  notes = ring(
    :g2, :d2, :g2, :f2, :gs2, :a2, :d2
  )

  duration = ring(
    0.5, 1.5, 0.25, 0.5, 0.25, 0.25, 0.75
  )

  7.times do
    tick
    midi notes.look
    sleep duration.look
  end
end

define :drum4 do
  use_midi_defaults channel: 5, sustain: 0.25

  notes = ring(
    :g2, :d2, :gs2, :g2, :f2, :gs2, :a2, :d2,
    :g2, :d2, :e2, :g2, :f2, :gs2, :a2, :d2, :b2
  )

  duration = ring(
    0.5, 0.5, 1, 0.25, 0.5, 0.25, 0.25, 0.75,
    0.5, 0.75, 0.75, 0.25, 0.5, 0.25, 0.25, 0.25, 0.5
  )

  17.times do
    tick
    midi notes.look
    sleep duration.look
  end
end

define :drum5 do
  use_midi_defaults channel: 5, sustain: 0.25

  notes = ring(
    :g2, :d2,
    :gs2, :e2, :b1,
    :g2, :f2, :cs3, :gs2,
    :a2, :d2, :gs2,
    :g2, :d2,
    :gs2, :e2, :b2,
    :g2, :f2, :gs2,
    :a2, :d2, :b2
  )

  duration = ring(
    0.5, 0.5, 
    0.25, 0.5, 0.25, 
    0.25, 0.25, 0.25, 0.25, 
    0.25, 0, 0.75, 
    0.5, 0.5, 
    0.25, 0.5, 0.25, 
    0.25, 0.5, 0.25, 
    0.25, 0.25, 0.5
  )

  23.times do
    tick
    midi notes.look
    sleep duration.look
  end

  /
    midi :g2
    sleep 0.5
    midi :d2
    sleep 0.5
    
    midi :gs2
    sleep 0.25
    midi :e2
    sleep 0.5
    midi :b1
    sleep 0.25
    
    midi :g2
    sleep 0.25
    midi :f2
    sleep 0.25
    midi :cs3
    sleep 0.25
    midi :gs2
    sleep 0.25
    
    midi :a2
    sleep 0.25
    midi :d2
    sleep 0
    midi :gs2
    sleep 0.75
    
    midi :g2
    sleep 0.5
    midi :d2
    sleep 0.5
    
    midi :gs2
    sleep 0.25
    midi :e2
    sleep 0.5
    midi :b2
    sleep 0.25
    
    midi :g2
    sleep 0.25
    midi :f2
    sleep 0.5
    midi :gs2
    sleep 0.25
    
    midi :a2
    sleep 0.25
    midi :d2
    sleep 0.25
    midi :b2
    sleep 0.5
  /
end

define :drum6 do
  use_midi_defaults channel: 5, sustain: 0.25

  notes = ring(
    :g2, :d2,
    :gs2, :e2, :b1,
    :g2, :f2, :cs3, :gs2,
    :a2, :d2, :gs2, :b1,
    :g2, :d2,
    :gs2, :as2, :e2, :b2,
    :g2, :f2, :cs3, :gs2,
    :a2, :d2, :gs2, :d2, :b2, :d2
  )

  duration = ring(
    0.5, 0.5,
    0.25, 0.5, 0.25,
    0.25, 0.25, 0.25, 0.25,
    0.25, 0, 0.25, 0.5,
    0.5, 0.5,
    0, 0.25, 0.5, 0.25,
    0.25, 0.25, 0.25, 0.25,
    0, 0.25, 0, 0.25, 0, 0.5
  )

  29.times do
    tick
    midi notes.look
    sleep duration.look
  end

  /
    #1
    midi :g2
    sleep 0.5
    midi :d2
    sleep 0.5
    
    #2
    midi :gs2
    sleep 0.25
    midi :e2
    sleep 0.5
    midi :b1
    sleep 0.25
    
    #3
    midi :g2
    sleep 0.25
    midi :f2
    sleep 0.25
    midi :cs3
    sleep 0.25
    midi :gs2
    sleep 0.25
    
    #4
    midi :a2
    sleep 0.25
    midi :d2
    midi :gs2
    sleep 0.25
    midi :b1
    sleep 0.5
    
    #5
    midi :g2
    sleep 0.5
    midi :d2
    sleep 0.5
    
    #6
    midi :gs2
    midi :as2
    sleep 0.25
    midi :e2
    sleep 0.5
    midi :b2
    sleep 0.25
    
    #7
    midi :g2
    sleep 0.25
    midi :f2
    sleep 0.25
    midi :cs3
    sleep 0.25
    midi :gs2
    sleep 0.25
    
    #8
    midi :a2
    midi :d2
    sleep 0.25
    midi :gs2
    midi :d2
    sleep 0.25
    midi :b2
    midi :d2
    sleep 0.5
  /
end

live_loop :cc_tuner do
  stop
  tick

  cc10 = (line 70, 50, steps: 100).mirror.look
  midi_cc 20, cc10, channel: 1
  sleep 1
end

live_loop :cc_control, sync: :tick do
  ##| stop
  tick

  cc3 = (line 10, 30, steps: 40).mirror.look
  midi_cc 13, cc3, channel: 1
  
  cc4 = (line 0, 127, steps: 200).mirror.look
  midi_cc 14, cc4, channel: 1
  
  cc5 = (line 127, 0, steps: 200).mirror.look
  midi_cc 15, cc5, channel: 1
  
  cc6 = (line 50, 70, steps: 100).mirror.look
  midi_cc 16, cc6, channel: 1
  
  cc7 = (line 70, 50, steps: 100).mirror.look
  midi_cc 17, cc7, channel: 1
  
  sleep 1
end

/
    Phrase controller:
    - Select which phrase will play
    - Determine how many times a phrase will play before selecting next phrase
    - Create live_loops that use phrase variable to determine the changes in the 
/

phrase = 0

live_loop :phrase_control, sync: :tick do
  ##| stop

  phrase_length = [20, 40, 60, 80, 100].choose*[1,2,4].choose
    # phrase_length = 5

  phrase_sequence = [
    (knit -3, 4, -2, 1),
    (knit -3, 1, -2, 4, -1, 1),
    (knit -2, 1, -1, 4, 0, 1),
    (knit -1, 1, 0, 4, 1, 1),
    (knit 0, 1, 1, 4, 2, 1),
    (knit 1, 1, 2, 4, 3, 1),
    (knit 2, 1, 3, 4)
    ]

  phrase = phrase_sequence[phrase - 3].choose
  set :phrase, phrase

  sleep phrase_length
end

live_loop :cc_phrase do
    if get[:phrase] == -3 then
        sleep 1
      end
      if get[:phrase] == -2 then
        sleep 1
      end
      if get[:phrase] == -1 then
        sleep 1
      end
      if get[:phrase] == 0 then
        tick
    
        cc1 = (line 50, 90, steps: 400).mirror.look
        midi_cc 11, cc1, channel: 1
        
        cc2 = (line 90, 70, steps: 400).mirror.look
        midi_cc 12, cc2, channel: 1
        
        cc8 = (line 30, 60, steps: 100).mirror.look
        midi_cc 18, cc8, channel: 1
        
        cc9 = (line 60, 90, steps: 100).mirror.look
        midi_cc 19, cc9, channel: 1
        
        cc10 = (line 50, 100, steps: 200).mirror.look
        midi_cc 20, cc10, channel: 1
        
        sleep 1
      end
      if get[:phrase] == 1 then
        tick 
    
        cc1 = (line 50, 70, steps: 400).mirror.look
        midi_cc 11, cc1, channel: 1
        
        cc2 = (line 90, 70, steps: 400).mirror.look
        midi_cc 12, cc2, channel: 1
        
        cc8 = (line 40, 80, steps: 50).mirror.look
        midi_cc 18, cc8, channel: 1
        
        cc9 = (line 40, 70, steps: 100).mirror.look
        midi_cc 19, cc9, channel: 1
        
        cc10 = (line 50, 80, steps: 200).mirror.look
        midi_cc 20, cc10, channel: 1
        
        sleep 1
      end
      if get[:phrase] == 2 then
        tick
    
        /
        cc1 = (line 30, 50, steps: 400).mirror.look
        midi_cc 11, cc1, channel: 1
    
        cc2 = (line 90, 70, steps: 400).mirror.look
        midi_cc 12, cc2, channel: 1
        /
        cc8 = (line 80, 100, steps: 50).mirror.look
        midi_cc 18, cc8, channel: 1
        
        cc9 = (line 40, 90, steps: 100).mirror.look
        midi_cc 19, cc9, channel: 1
        /
        cc10 = (line 50, 80, steps: 200).mirror.look
        midi_cc 20, cc10, channel: 1/
        
        sleep 1
      end
      if get[:phrase] == 3 then
        ##| repeats = 4
        sleep 1
      end
end

live_loop :choir_phrase, sync: :tick do

    if get[:phrase] == -3 then
      choir
    end
    if get[:phrase] == -2 then
      choir
    end
    if get[:phrase] == -1 then
      choir
    end
    if get[:phrase] == 0
      choir
    end
    if get[:phrase] == 1 then
      choir
    end
    if get[:phrase] == 2 then
      choir
    end
    if get[:phrase] == 3 then
      choir
    end
end
  
live_loop :bass_phrase, sync: :tick do
    if get[:phrase] == -3 then
      bass 4
    end
    if get[:phrase] == -2 then
      bass 4
    end
    if get[:phrase] == -1 then
      bass 4
    end
    if get[:phrase] == 0
      bass 2
    end
    if get[:phrase] == 1 then
      bass 4
    end
    if get[:phrase] == 2 then
      bass 4
    end
    if get[:phrase] == 3 then
      bass 4
    end
end
  
live_loop :guit_phrase, sync: :tick do
    if get[:phrase] == -3 then
      guit 2, 2, 1, (knit 1, 2, 2, 1).choose
    end
    if get[:phrase] == -2 then
      guit 1, 1, 4, 2
      if one_in(2)
        sleep (knit 4, 1, 8, 2, 16, 1).choose
      end
    end
    if get[:phrase] == -1 then
      guit 1, 0, 0, (knit 4, 1, 8, 4).choose
      if one_in(1)
        sleep (knit 4, 1, 8, 2, 16, 4).choose
      end
    end
    if get[:phrase] == 0
      sleep 1
    end
    if get[:phrase] == 1 then
      sleep 1
    end
    if get[:phrase] == 2 then
      sleep 1
    end
    if get[:phrase] == 3 then
      sleep 1
    end
end
  
live_loop :drum_phrase do
    if get[:phrase] == -3 then
      sleep 1
    end
    if get[:phrase] == -2 then
      sleep 1
    end
    if get[:phrase] == -1 then
      sleep 1
    end
    if get[:phrase] == 0 then
      drum1
    end
    if get[:phrase] == 1 then
      drum2
    end
    if get[:phrase] == 2 then
      drum3
      ##| drum4
    end
    if get[:phrase] == 3 then
      drum5
      drum6
    end
end

```

So the two places where I’m still encountering some issues are:

1. Occasionally, when the phrase is -3 or 3, it will jump to the other end (-3 → 3, 3 → -3). I’m not sure what’s causing this… Could this be an absolute value thing? Something to do with the fact that the phrases pass into the negative (I don’t think I will do that for future code, but I do think it was useful for this particular track as a demonstration that there are two poles that are musically different ideas that the code is oscillating between).
2. The drum patterns for `drum5` and `drum6` are a bit wonky, and I can’t tell why. Perhaps a second set of eyes could be useful! I left in the original timings commented out. I’m wondering if this has to do with the fact that there are at times notes that are suppose to play at the same time? In those cases, I simply added a `0` to the `duration` ring, but even as I was doing that, it felt off.

Thanks again for all your help. I’m going to make a video soon walking folks through the code and I’ll definitely shout you out! You’ll also be able to hear what the track sounds like in the background.

---

<div class="post-metadata">

**Author:** ![pashultz](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/pashultz/32/1964_2.png) [@pashultz](https://in-thread.sonic-pi.net/u/pashultz)\
**Post date:** [June 14, 2024, 9:32pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/7 "2024-06-14T21:32:57Z")

</div>

Excellent! I’m pretty sure the phrase change problem is just because of a typo. There should be a `+` instead of a `-` here:

```ruby
phrase = phrase_sequence[phrase - 3].choose
  set :phrase, phrase

```

As for `drum5` and `drum6`, I think you’re right about the 0-duration sleeps. I believe that will cause the loop to try to `tick` twice at the same time, which would lead to undefined behavior—perhaps even wonky behavior. Hmm.

Unfortunately, I don’t see an elegant fix. I think the idiomatic SP way would be to put the two simultaneous notes into an array, since you can `play [:d2, :gs2]` with the built-in synths. Unfortunately you can’t `midi [:d2, :gs2]` in the same way. (I’d consider this a bug, but it’s been known for ages, so maybe they prefer it this way for some reason?) But you can work around it by writing a little handler for arrays:

```ruby
define :drum5 do
  use_midi_defaults channel: 5, sustain: 0.25
  
  notes = ring(
    :g2, :d2,
    :gs2, :e2, :b1,
    :g2, :f2, :cs3, :gs2,
    :a2, [:d2, :gs2],
    :g2, :d2,
    :gs2, :e2, :b2,
    :g2, :f2, :gs2,
    :a2, :d2, :b2
  )
  
  duration = ring(
    0.5, 0.5,
    0.25, 0.5, 0.25,
    0.25, 0.25, 0.25, 0.25,
    0.25, 0.75,
    0.5, 0.5,
    0.25, 0.5, 0.25,
    0.25, 0.5, 0.25,
    0.25, 0.25, 0.5
  )
  
  23.times do
    tick
    n = notes.look
    if n.respond_to?('each')
      n.each do |x|
        midi x
      end
    else
      midi n
    end
    sleep duration.look
  end
end

```

Can’t wait to hear it! And thanks so much for the shout-out!

---

<div class="post-metadata">

**Author:** ![pashultz](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/pashultz/32/1964_2.png) [@pashultz](https://in-thread.sonic-pi.net/u/pashultz)\
**Post date:** [June 14, 2024, 10:50pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/8 "2024-06-14T22:50:34Z")

</div>

> A lot of the synths that I use (especially for generative compositions) are using Surge XT, an open-source synth/VST. I’d definitely recommend it, and they have a Linux build.

Oh yeah! I’ve installed it and tried a couple of the presets but not really messed around with it. Need to do more, clearly.

> \*side note: I feel weird calling these “tracks” do we have another word to describe our coded music? Particularly when it’s generative and not confined to a set length of time?

Great question. Probably depends on how deterministic and interactive it is: I’ve used/heard “works,” “pieces,” “systems,” “processes,” (in descending order of determinism) maybe even “virtual ensembles” or “hyperinstruments” if they’re interactive and I’m feeling fanciful. Though when I’m talking to people who are NOT older and wealthier than me, they’re “things.” 🤷

---

<div class="post-metadata">

**Author:** ![zir0](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/zir0/32/2096_2.png) [@zir0](https://in-thread.sonic-pi.net/u/zir0)\
**Post date:** [June 15, 2024, 5:17am UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/9 "2024-06-15T05:17:28Z")

</div>

> There should be a `+` instead of a `-` here:

🤦  
Right, that makes sense.

> Unfortunately, I don’t see an elegant fix. I think the idiomatic SP way would be to put the two simultaneous notes into an array, since you can `play [:d2, :gs2]` with the built-in synths. Unfortunately you can’t `midi [:d2, :gs2]` in the same way. (I’d consider this a bug, but it’s been known for ages, so maybe they prefer it this way for some reason?) But you can work around it by writing a little handler for arrays

Okay, I’m going to try this, I just want to understand it so I’m going to try to break it down:

```ruby
if n.respond_to?('each')
      n.each do |x|
        midi x
      end
    else

```

Here, we’re using `n` instead of `notes` because in this if statement, `n` represents an array. `.respond_to?('each')` can only work with arrays so it’s essentially checking if it’s an array and saying “if there’s an array here with more than one item, then for each of these items (x) `midi x`.”

Is that about right?

---

<div class="post-metadata">

**Author:** ![pashultz](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/pashultz/32/1964_2.png) [@pashultz](https://in-thread.sonic-pi.net/u/pashultz)\
**Post date:** [June 15, 2024, 6:24am UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/10 "2024-06-15T06:24:44Z")

</div>

Exactly! Great explanation.  
[edited to add:] Here’s how I got there. I had seen examples of `midi_chord` functions on this forum that used the `.each` method. But we want something here that can handle both arrays and single notes, so we have to know when we can safely use `.each`. I’m actually not especially familiar with Ruby, so I did a web search for “ruby array test.” The second-ranked answer in [this StackOverflow thread](https://stackoverflow.com/questions/1527888/ruby-test-for-array) asks,

> Are you sure it **needs** to be an array? You may be able to use `respond_to?(method)` so your code would work for similar things that aren’t necessarily arrays…

… which got me thinking. I’ve suspected (but not really checked) that a lot of Sonic Pi’s types may be derived from the equivalent Ruby ones without necessarily being identical. We could probably use the `kind_of?` predicate suggested in the top answer to catch anything that inherits from `Array`, but here all we really need is something that can be `.each`-ed! So I went with that.

---

<div class="post-metadata">

**Author:** ![robin.newman](https://avatars.discourse-cdn.com/v4/letter/r/76d3ee/32.png) [@robin.newman](https://in-thread.sonic-pi.net/u/robin.newman)\
**Post date:** [June 15, 2024, 8:25pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/11 "2024-06-15T20:25:48Z")

</div>

Yes you use the respond to method. I have done that often.  
e.g.

```ruby
define :pmidi do |n,sus=1,vf = 0.2| #default for sus and v can be used
  if n.respond_to?(:each)
    n.each do |nv|
      midi nv,sustain: sus, vel_f: vf # no sleep between
    end
  else
    midi n, sustain: sus, vel_f: vf
  end
  sleep sus #sleep duration of note or list here
end

```

This will work for single note values or for two or three notes together in a list eg

```ruby
live_loop :test do
  pmidi [:c4,:e4] #default 1 beat
  pmidi 72,0.25,0.9 #one note, sustain 0.25,vel_f 0.9
end

```

Here is a fuller version I use when playing lsitrs of notes and duration which may include chords as well.

```ruby
#function to play midi notes/duration lists with optional transpose
define :mplay do |notes,durs,channel,tr,vel=100|
  for x in 0..notes.length-1
    if notes[x].respond_to?(:each)
      notes[x].each do |n|
        midi note(n)+tr,sustain: durs[x],channel: channel,velocity: vel
      end
    else
      midi note(notes[x])+tr,sustain: durs[x],channel: channel,velocity: vel
    end
    sleep durs[x]
  end
end

```

---

<div class="post-metadata">

**Author:** ![zir0](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/zir0/32/2096_2.png) [@zir0](https://in-thread.sonic-pi.net/u/zir0)\
**Post date:** [June 18, 2024, 8:35am UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/12 "2024-06-18T08:35:20Z")

</div>

I wasn’t able to get multiple notes to play using the method you’d outlined @pashultz, but I’ll give it another try, as well as attempt what you’re suggesting @robin.newman Thank you both!

I’ve uploaded a little tutorial explaining the concept of the “phrase controller” [here on YouTube](https://www.youtube.com/watch?v=P3jvoJQbH8I) in the hopes that this concept can help some more people! And I gave you a shout out @pashultz. You can also hear some of the track, it doesn’t really go into the more percussive section within the video, but you can still hear some of it.

---

<div class="post-metadata">

**Author:** ![pashultz](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/pashultz/32/1964_2.png) [@pashultz](https://in-thread.sonic-pi.net/u/pashultz)\
**Post date:** [June 20, 2024, 3:41pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/13 "2024-06-20T15:41:34Z")

</div>

Wow, that’s great. Now that you’ve explained the Cocteau/Dilla continuum, the symmetry of the phrase setup makes total sense. And it really does sound great—I just reinstalled Surge-XT. 😄 Next I need to go back and see how you’re choosing the pitches: the code seemed relatively simple, but it sounds like an expert improviser.

Did we get to hear the drums on that video? I’m very curious. When I think of “Dilla beats,” it’s usually something with swingy microtiming, which is _really_ fun to do in Sonic Pi.

The VSCode environment looks great too. Reminds me that there really are some basic/nice features missing from the SP editor: for a start, code folding and search, as you say.

---

<div class="post-metadata">

**Author:** ![zir0](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/zir0/32/2096_2.png) [@zir0](https://in-thread.sonic-pi.net/u/zir0)\
**Post date:** [July 1, 2024, 4:11pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/14 "2024-07-01T16:11:38Z")

</div>

Hey, sorry for the massive delay! I keep putting off responding until I’ve recorded a snippet of the Lupin Tulpa composition, which I keep putting off for other reasons, and on and on…

I don’t think my Dilla is anywhere close to _the_ Dilla, but I talk about it in that way given that that end of the spectrum is more percussive and the drum samples I’m using are from the Mt. Dill kit 😅 But definitely interested in implementing some more swing into the project!

---

<div class="post-metadata">

**Author:** ![HarryLeBlanc](https://dub1.discourse-cdn.com/flex017/user_avatar/in-thread.sonic-pi.net/harryleblanc/32/2813_2.png) [@HarryLeBlanc](https://in-thread.sonic-pi.net/u/HarryLeBlanc)\
**Post date:** [July 1, 2024, 11:34pm UTC](https://in-thread.sonic-pi.net/t/tips-on-simplifying-optimizing-code/8913/15 "2024-07-01T23:34:49Z")

</div>

Here’s a useful little function I cooked up to test whether an item is a ring or a list:

```ruby
define :ringorlist do |thisitem|
  thisitem.is_a? Enumerable or thisitem.is_a? SonicPi::Core::RingVector
end

```

Returns true or false.  
Hope you find this useful.
