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

1 Like

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

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

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…

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.