How would you "tighten" this code up?


#1

To learn Sonic Pi (and music making in general), I’m trying to translate some of my favorite music. Here’s what I’ve done with the chords from “Olson” by Boards of Canada:

# Boards of Canada OLSON chords
use_bpm 30
live_loop "chords" do
  with_synth :dpulse do
    play (chord :e3, :major), sustain: 1, amp: 0.7
    play :e2, sustain: 1, amp: 0.7
    sleep 1.5
    play (chord :fs3, :major), sustain: 0.5, amp: 0.7
    play :fs2, sustain: 0.5, amp: 0.7
    sleep 0.5
    play (chord :cs3, :major), sustain: 1, amp: 0.7
    play :cs2, sustain: 1, amp: 0.7
    sleep 2
  end
end

Works, sounds good, but looks kind of ugly to me. I’m duplicating a lot of sustains and amps and stuff. How would you all go about “tightening” this code up? That is, making it more compact or concise while retaining flexibility.


#2

Hi Matt,

Look into ‘use_synth_defaults’

Set them to the most commonly repeated,
and let any others simply override the defaults.

Eli…

# Boards of Canada OLSON chords
use_bpm 30
live_loop "chords" do
  use_synth_defaults sustain: 1, amp: 0.7
  with_synth :dpulse do
    play (chord :e3, :major)
    play :e2
    sleep 1.5
    play (chord :fs3, :major), sustain: 0.5
    play :fs2, sustain: 0.5
    sleep 0.5
    play (chord :cs3, :major)
    play :cs2
    sleep 2
  end
end

#3

Still not quite it but you can definitely fix this pretty easily:

# Boards of Canada OLSON chords
live_loop :chords do ; tick
  use_bpm 30
  use_synth :dpulse
  my_chords = (ring :e3, :fs3, :cs3).look
  play (chord my_chords, :major), sustain: (ring 1, 1, 0.5, 1).look, amp: 0.7
  play (ring :e2, :fs2, :cs2).look, sustain: (ring 1, 0.5, 1).look, amp: 0.7
  sleep (ring 1.5, 0.5, 2).look
end

#4

Cool, I’ve pulled all the amps to the use_synth_defaults command and now it’s easy to mix the volume of the chords down when the lead line starts up.

Edit: can you set a synth’s argument to a factor of the default value? Could I do use_synth_defaults amp: 0.5 and then use something like “amp: $default * 1.5” on the second note to accent it to 0.75?


#5

Ah, I had tried messing with play_pattern_timed, but couldn’t change the sustain per note.

“tick” happens first, but it pulls the first values from the rings (instead of the second). I would have thought you’d put a tick after the notes were played to increment to the next. Any insights on why this works this way?


#6

I’m afraid that I don’t really understand your question. Can you explain this again?


#7

I’m probably making a bad assumption somewhere.

The first thing your loop does is “tick”. I believe that by default it is initialized to 0 and any call to “tick” adds 1. So the first time you “.look” you should be looking at the second element of the ring “array”. As an old C programmer, I count from 0, and 1 would be the second element. My instinct would be to put “tick” at the end of the loop, to increment it for the next time.

But in execution, what you gave works perfectly well.


#8

It probably seems a little counter-intuitive to begin with, but the key is in the details of the ‘tick’ section of the tutorial - See http://sonic-pi.net/tutorial#section-9-4. Just under where it describes ‘look’ it says this:

The magical thing about tick is that not only does it return a new index (or the value of the ring at that index) it also makes sure that next time you call tick, it’s the next value.

(Emphasis mine)

@samaaron may be able to explain more if you are still curious about it…


#9

I need to apologize, I should have done more testing with using “tick” as I thought it should be used.

When I moved the tick to the end I found that the first chord played first, just as I expected, but it also played on the second iteration too, and then progressed through the ring as I expected. I think I have a better handle on it now. “tick” is different than a typical index in a loop that you would increment.


#10

Oh, I get it now. Yes, tick is a bit special. My computer science knowledge is made only of doing things with live-coding languages so I guess that I was not vigilant enough to spot that this was a strange behavior in the first place. Here is some advices concerning tick and .look:

Everytime you feel that some kind of repetition will happen in your code, and that you need to make it as concise as possible to keep it in control during a performance, I would suggest using a resonable amount of looping rings, thanks to .look. I may use them a bit too much, here is a typical thing I could wrote using ring, knit and line:

live_loop :looping do ; tick
  with_fx :bitcrusher, bits: (line 1, 5, steps: 10).reflect.look do
    play (ring :r, :r, :r, :a4).look
    play (ring :r, :r, :g4, :r).look
    play (ring :r, :e4, :r, :r).look
    play (ring :c4, :r, :r, :r).look
    play (knit :c2, 4, :a2, 4).look
    sleep (ring 0.25, 0.5).look
end ; end

Oh, and by using conditionals, you should be able to use multiple ticks that increments only when you need them to do so. That is also one of the only ways I found to truly stay in control of every parameter while looping easily.


#11

Here’s my take:

use_bpm 30

define :olson do | nt, sus, slp|
  play (chord nt, :major), sustain: sus
  play nt - 12, sustain: sus
  sleep slp
end

live_loop :chords do
  use_synth :dpulse
  use_synth_defaults amp: 0.7
  olson(:e3, 1, 1.5)
  olson(:fs3, 0.5, 0.5)
  olson(:cs3, 1, 2)
end

That’s trying to make it as Ruby DRY (Don’t Repeat Yourself) as possible. It’s probably a little extra DRY and tailored specifically to your question, but perhaps this is a way you’ll like to work if you plan on a lot of chords set up like this.

Love me some BoC!


#12

Nice. I should spend more time working with definitions.
I never used Ruby before. I’m a C and Python guy. Are there good Ruby tutorials that would help with Sonic Pi?


#13

The Reason I love Ruby, besides Matz of course, is because of its definitive bible. Why’s Poignant Guide to Ruby is one of the finest pieces of programming literature I’ve ever read. Why was a brilliant and funny teacher who was passionate about Ruby and left his mark behind. When I speak of him in the past tense, he retired from the community after his identity became known, he simply preferred to be an Anonymous guy who drew cartoon foxes to teach about Ruby.

The document will always be free and available and the underlying information never goes out of style. https://poignant.guide/

As far as Sonic Pi specific resources, as it is more like a Ruby library, all the best info is already built in between the tutorial series and the language reference. Learning core Ruby can really help with some of the out of the box things you can do, but pretty mucheverything I do in Sonic Pi is an adaptation of things I learned in the language reference. To be honest, I derped on my syntax above and opened up the guide, I forgot that SPi doesn’t seem to like “def” and that functions are named as labels rather than variables. Even in practice I swear by the Help system, Sam did a bang up job on it.

Also just to note, I don’t type Sonic Pi DRY in practice, and I rarely use define. But when you read through the poignant guide, that would be the idiomatic way to do it for the least repetition possible, which is part of the Ruby style guide. I’m actually quite verbose in SPi unless I need to control a lot of parameters in relation to each other and then refactor it down if I share it so it isn’t overwhelming. I’ve only just begun naming my midi ports even, so glad I finally did.