Sections / song structure definition

has anyone got any ideas on how to make a “structured song” ? I’ve been working on trying to make a useable / “easy to follow” tick counter that can define were you are in a song (intro, break, bridge, chorus, verse, outro, end (etc))… I was also playing with making beats with a string (instead of an array) so that’s sprinkled in here too… any use?


use_cue_logging false
use_debug false
use_bpm 100
intro = false

# arrays that hold references to samples
bds = [0, :drum_bass_hard, :bd_boom]
sns = [0, :sn_dolf, :sn_generic]
hts = [0, :drum_cymbal_pedal, :drum_cymbal_closed, :drum_cymbal_open]

# strings that represent beat patterns (numbers refer to sample positions in sample arrays (above)
bd0 = "20000000"
sn0 = "0"
ht0 = "00100001"
bd1 = "10020000"
sn1 = "00001000"
ht1 = "0010"
bd2 = "1002000011020000"
sn2 = "0000010000100000"
ht2 = "0010001000100100"
bd3 = "01"
sn3 = "10"
ht3 = "0001"

# set up 2 variables to keep track of current & old "section"
@sec = :intro
@oldSec = :intro

# function to change the "global" state of playing
define :setPlay do | tf |
  @playing = tf
end

#set playing = true (could put it into a defonce call if you want)
@playing = true

# hash to keep track of "sections" where times & specific beats for that section are kept.
sections = {
  :intro  => {:time => [0,1], :bp => bd0, :sp => sn0, :hp => ht0},
  :break  => {:time => [5,13], :bp => bd2, :sp => sn2, :hp => ht2},
  :main   => {:time => (2..16).to_a, :bp => bd1, :sp => sn1, :hp => ht1},
  :end    => {:time => [17], :bp => bd3, :sp => sn3, :hp => ht3},
}

# function that takes a list & a number so it can play a beat from a sample in that list (l) at position (x)
define :beatX do | l, x |
  # l = list of samples | x = beat
  # if x isn't 0, we play the sample from the list passed
  if x!=0 and l
    sample l[x]
  end
end

# live loop to control what section we're in
live_loop :sectionCheck do
  t = tick
  # find the section based on the Tick position in "sections" ranges
  @sec = sections.select{|k,v| v[:time].include? t}.keys()[0]
  if sections.keys().include? @sec
    puts "tick = #{t}"
    if @sec != @oldSec
      puts "section change to #{@sec}"
      @oldSec = @sec
    end
  else
    setPlay(false)
    stop
  end
  sleep 4 # 4 beats per bar
end

# live loop that calculates what beat to play from the tick position
live_loop :beats do
  if @playing == true
    t = tick
    #bassdrums
    beatX(bds, sections[@sec][:bp].chars().ring[t].to_i)
    #snaredrums
    beatX(sns, sections[@sec][:sp].chars().ring[t].to_i)
    #hi hats
    with_fx :level, amp: 0.4 do
      beatX(hts, sections[@sec][:hp].chars().ring[t].to_i)
    end
    sleep 0.25 # standard 1/4 beat sleep
  else
    stop
  end
end

Hi,

If you don’t mind to watch a video in French, you can give a try at this video Sonic Pi - coder la structure d'un morceau - #04 - YouTube

The repo gives you all the audio material used in this project. GitHub - epnlespieux/sample-your-life-with-sonic-pi

I make this for a classroom with 14 years old pupils.
Cheers

4 Likes

merci!
yeah, that’s a good way of creating “sections” too (as functions). Cheers!

hi,

your code is very inspiring but i would say more for coders people :slight_smile: with a ruby spirit !

Cheers

1 Like

My two cents: this is a little script that will play a predefined structure (drums and piano).

use_synth :sine

INTRO = {'name' => 'intro', 'bars' => 4}
VERSE = {'name' => 'verse', 'bars' => 16}
CHORUS = {'name' => 'chorus', 'bars' => 16}
OUTRO = {'name' => 'outro', 'bars' => 4}

structure = [
  INTRO,
  VERSE,
  CHORUS,
  VERSE,
  CHORUS,
  VERSE,
  CHORUS,
  OUTRO
]

define :play_drums do |segment|
  case segment['name']
  when 'intro'
    sample :drum_cymbal_closed
  when 'verse'
    case look%4
    when 0
      sample :drum_heavy_kick
    when 2
      sample :drum_snare_hard
    else
      sample :drum_cymbal_closed
    end
  when 'chorus'
    case look%4
    when 2
      sample :drum_cymbal_closed
    end
  when 'outro'
    case look%4
    when 0, 1, 2
      sample :drum_heavy_kick
    end
  end
end

define :play_piano do |segment|
  #puts lk
  case segment['name']
  when 'chorus'
    case look%4
    when 0
      play chord(:b3, :minor)[0], decay: 0.2
    end
  when 'verse'
    case look%4
    when 0
      play chord(:a3, :major)[0]
    when 1
      play chord(:a3, :major)[1]
    when 2
      play chord(:a3, :major)[2]
    end
  end
end

for segment in structure do
    segment['bars'].times do
      tick
      play_drums(segment)
      play_piano(segment)
      sleep 0.25
    end
  end
2 Likes

I usually use a tracking array to check which sound loops I have active (producing a sound)
and inactive:

tracker = [0,1,1,0,0,0,0]

See the explanation/example in this post:

Eli…

2 Likes

Hi,

good two cents :slight_smile:.
Maybe some comments would help people to understand quicker your code and learn them the use of syntax ruby.
Cheers

1 Like

Great advice, thanks. I will add some comments to future posts.

Cheers (Is that directed to me?)
It’s been an interesting journey (for me) in Sonic Pi… you know that “overwhelming” feeling of looking at everything that’s possible (but looks like the Matrix code!)… and, having progressively getting more confident with building on basic methods / ways of doing stuff… I started pushing “how to make things happen more efficiently” (I think! I’m not a coder by trade, started as a graphic designer, ending up in “multimedia” Director / Flash for early programming experience)…
So, I’d say that if it looks complicated or “for coders” I’ll take that as a compliment, but also say “it’s easily obtainable” in a short space of time when using the versatile Sonic Pi environment
yours,
Phil.

Merci @nlb… Exactement ce que je cherchais…
This works just great!

2 Likes