Help with melodic basslines?

Hi! I’m new to this, I’m starting to program music with Sonic Pi, and I’m having a lot of fun! I have a few questions, if anyone here knows the software, I need some help with something.

I’m trying to create a bassline that follows the note of the chord that’s currently playing. I’d like to do this without having to write each note individually. I know there has to be a much simpler way.

I’ve attached a screenshot so you can see how the code is written for now.

I initially thought I could do it with “if” statements, but I couldn’t find a way to program it correctly. Any ideas? Thanks!

Hi

Welcome to Sonic Pi! Because a chord is a ring array, we can target individual chord tones by index (to derive a bassline from a given chord); 0 = root, 1 = 3rd, 2 = 5th etc:

live_loop :tonic do
  play chord(:e, :minor)[0], release: 0.1
  sleep 0.5
end

HTH

PD-Pi

[EDIT]

You can get the bassline to follow the chord progression like this (apologies if a bit clunky, haven’t used Sonic Pi in a while):

triads = [":e3",":a3","b3",":e3"].ring

use_synth :fm
live_loop :chord do
  tick
  play chord(triads.look, :minor)+12
  sleep 2
end

use_synth :bass_foundation
use_synth_defaults pitch: -12
live_loop :root do
  tick
  note = chord(triads.look, :minor)[0]
  4.times do
    play note, release: 0.6
    sleep 0.5
  end
end

PD-Pi

1 Like

Okay! That works, thank you so much! I’ve been very busy these past few days and haven’t had time to reply quickly. I have a few questions about what you wrote.

Why do you put a “tick” command before each “play”? What does it do?

Does putting “.ring” at the end of the square brackets turn the content into a ring? Is it the same as using “(ring)”?

Hi

tick is a thread/loop-independent counter, which reads through the ring array of named chords called ‘triads’ via .look. This generates a chord sequence (the second loop simply targets the root note of each chord, see: Help/Lang/tick). Without the tick, each loop would only target the first element in that ring array. And yes, .ring is exactly the same as (ring).

PD-Pi

Just to clarify, I think what Brendan means here by ‘thread/loop-independent’ is ‘isolated per-thread/loop’, rather than ‘isolated from any thread/loop’.
(I think the word Sam often used was ‘Thread-local’ (i.e. local to the thread/loop).

1 Like

Thanks for refining my clumsy syntax :wink:

Also, remember that notes are just numbers, and you can do math. If you want the note to be 2 octaves below the first note of the chord, just do

live_loop :tonic do
  play chord(:e, :minor)[0] - 24, release: 0.1
  sleep 0.5
end

Have fun exploring sonic pi! It’s a blast!

One thing that hasn’t been mentioned and is worth knowing early: in the two-loop version above, the bass loop and the chord loop each keep their own tick counter, and they only stay in agreement because the sleep times happen to line up (2 beats of chord equals 4 x 0.5 beats of bass). The moment you change one loop’s timing, or restart one of them while the other is running, they silently drift apart and the bass follows the wrong chord.

Since tick is local to each live_loop, the robust fix is to have a single authority for “which chord are we on”. The idiomatic Sonic Pi way is a cue: make the chord loop the boss and have it announce the chord, and have the bass loop wait for that announcement, so it can’t drift by construction.

triads = (ring :e3, :a3, :b3, :e3)

live_loop :harmony do
  c = chord(triads.tick, :minor)
  cue :chord_change, root: c[0]
  use_synth :fm
  play c + 12
  sleep 2
end

live_loop :bassline do
  info = sync :chord_change
  use_synth :bass_foundation
  4.times do
    play info[:root] - 12, release: 0.6
    sleep 0.5
  end
end

Note the bass loop has no tick and no hardcoded chord list at all: it only knows what it was told. Change the progression, change the harmonic rhythm, reorder the ring, and the bass follows automatically. sync also blocks until the cue arrives, so the loops start in phase even if you evaluate the buffer mid-bar.

On the melodic side of your question, once you have the root you can walk around the chord rather than hammering one note. Indexing the chord keeps everything consonant for free:

c = chord(triads.look, :minor)
pattern = (ring 0, 0, 2, 1)   # root, root, fifth, third
play c[pattern.tick] - 12

And as HarryLeBlanc says, notes are just numbers, so minus 12 is an octave down, plus 7 is a fifth up, and adding a passing note is arithmetic rather than another chord lookup. If you want something less rigid, picking from (scale :e3, :minor_pentatonic) with .choose on the weak beats while keeping chord tones on the strong beats gives you movement without ever landing out of key.

1 Like

An alternative way to maintain concordance between harmony and bass line is to use global variables. Even if we break the time signature, the bass will still play the root note of the chord that was played.

triads = [":e3",":a3","b3",":e3"].ring

use_synth :fm
live_loop :harmony do
  tick
  set :current_triad, triads.look
  play chord(triads.look, :minor)+12
  sleep 2
end

use_synth :bass_foundation
use_synth_defaults pitch: -12
live_loop :bassline do
  4.times do
    note = chord(get[:current_triad], :minor)[0]
    play note, release: 0.6
    sleep 0.5
  end
end