So, I had a go at merging everything in here together: now it will prefer a chord fingering passed in to the backing track if available, otherwise if it’s in the list of standard chords (using the list generated by @strickinato) and using standard tuning it will use that, otherwise it will fall back to generating it with my code.
I removed chords from the standard list that are generated the same by my code, in order to keep the size down. I was actually surprised by how many don’t match - I might be able to use these examples to improve my algorithm.
I also removed the BackingTrack class, and just passed the parameters into a function. This was mainly because with it included, the code became too long to run in a single buffer. You can still combine all track parameters into a hash object if you want to keep them together (as I’ve done with the Strokes example).
The way the tuning works is slightly different than before - now it will adjust the fingering to still try to generate the named chord, so the Nirvana example had to be tweaked a bit - for this I added a transpose option to shift the chords up or down a specified number of semitones.
I also added lower-case u
and d
strokes, which are the same as U
and D
, but just a bit quieter, so you can more easily add some basic dynamics.
Oh, and I added one more example track.
One thing I found quite fun, was to take one of the example tracks, and just add a tuning: ukulele_tuning
parameter, and it converts it into a ukulele version!
Here’s the code:
guitar_tuning = [:e2, :a2, :d3, :g3, :b3, :e4]
ukulele_tuning = [:g, :c, :e, :a]
guitar_chords = {
guitar_tuning => {
"F5"=> "133xxx",
"Bb5"=> "688xxx",
"Db5"=> "x466xx",
"Ab" => "466544",
"Ab5"=> "466xxx",
"Abm" => "466444",
"Ab7" => "464574",
"Abmaj7" => "4x554x",
"Abm7" => "464444",
"Abdim" => "xx6434",
"Amaj7" => "x02224",
"Adim" => "x0121x",
"A#" => "x13331",
"A#7" => "x13131",
"A#maj7" => "x13231",
"Bb" => "x13331",
"Bb7" => "x13131",
"Bbmaj7" => "x13231",
"B" => "x24442",
"Bm" => "x24432",
"B7" => "x24242",
"Bmaj7" => "x24342",
"Bm7" => "x20202",
"Bdim" => "x2343x",
"Cm" => "x35543",
"C7" => "x32310",
"Cmaj7" => "x35453",
"Cm7" => "x35343",
"Cdim" => "x3454x",
"C#m" => "x46654",
"C#7" => "x46464",
"C#maj7" => "x46564",
"C#m7" => "x42100",
"Dbm" => "x46654",
"Db7" => "x46464",
"Dbmaj7" => "x46564",
"Dbm7" => "x42100",
"Ddim" => "xx0131",
"D#" => "x65343",
"D#7" => "xx1323",
"D#maj7" => "xx1333",
"D#dim" => "x6787x",
"Eb" => "x65343",
"Eb7" => "xx1323",
"Ebdim" => "x6787x",
"Emaj7" => "0x2444",
"Edim" => "x7x986",
"F" => "133211",
"F7" => "131241",
"Fmaj7" => "1x2210",
"Fdim" => "1x310x",
"F#" => "244322",
"F#m" => "244222",
"F#7" => "242352",
"F#maj7" => "2x332x",
"F#m7" => "242222",
"F#dim" => "133211",
"Gb" => "244322",
"Gbm" => "244222",
"Gb7" => "242352",
"Gbmaj7" => "2x332x",
"Gbm7" => "242222",
"Gbdim" => "xx4212",
"G" => "320033",
"Gm" => "355333",
"G7" => "320001",
"Gmaj7" => "3x443x",
"Gm7" => "353333",
"Gdim" => "xx5686",
"G#" => "466544",
"G#m" => "466444",
"G#7" => "464574",
"G#maj7" => "4x554x",
"G#m7" => "464444",
"G#dim" => "320033",
}}
define :next_note do |n, c|
# Make sure n is a number
n = note(n)
# Get distances to each note in chord, add smallest to base note
n + (c.map {|x| (note(x) - n) % 12}).min
end
# Return ring representing the chord chrd, as played on a guitar with given tuning
define :guitar_chord do |tonic, name, tuning=guitar_tuning|
chrd = (chord tonic, name)
# For each string, get the next higher note that is in the chord
c = tuning.map {|n| next_note(n, chrd)}
# We want the lowest note to be the root of the chord
root = note(chrd[0])
first_root = c.take_while {|n| (n - root) % 12 != 0}.count
# Drop up to half the lowest strings to make that the case if possible
if first_root > 0 and first_root < tuning.count / 2
c = (ring :r) * first_root + c.drop(first_root)
end
c
end
define :split_chord do |guitar_pattern, tuning|
guitar_pattern.split(//).zip(tuning).map{|curr_string, t|
curr_string=='x' ? nil : t + curr_string.to_i
}
end
define :chord_notes do |chord_name, fingerings, tuning|
tuning ||= guitar_tuning
if fingerings && fingerings[chord_name]
notes = split_chord(fingerings[chord_name], tuning)
elsif guitar_chords[tuning] and guitar_chords[tuning][chord_name]
notes = split_chord(guitar_chords[tuning][chord_name], tuning)
else
m = /([A-G][b#]?)(.*)/.match(chord_name)
tonic = m[1].sub('#', 's')
name = m[2].empty? ? "M" : m[2].sub('maj7', 'major7')
notes = guitar_chord tonic, name, tuning
end
notes.filter{|n| ![nil,:r].include? n}.ring
end
define :single_stroke do |notes, stroke, midi|
amp = 1
time = 0.03
rel = 1.6
if stroke == 'D' # Down stroke
elsif stroke == 'd' # Down stroke (quieter)
amp = 0.7
elsif stroke == 'U' # Up stroke
notes=notes.reverse.take(3)
elsif stroke == 'u' # Up stroke (quieter)
notes=notes.reverse.take(3)
amp = 0.7
elsif stroke == 'x' # Down stroke
time = 0.001
rel = 0.2
elsif stroke == '.'
else
puts "Unrecognosed stroke:" + stroke
stop
end
if stroke != '.'
in_thread do
if midi
notes.each do |n|
midi n, sustain: rel, channel: 3, vel: amp * 127
sleep time
end
else
play_pattern_timed notes, time, release: rel, amp: amp
end # end if stroke
end
end
end
define :play_strumming do |notes,strumming_pattern,midi|
strumming_pattern.split(//).each do |stroke|
single_stroke notes, stroke, midi
sleep 0.5
end
end
define :play_backing_track do |chords: [], pattern: ["D.DU.U.D",".DU.DU.."], bpm: nil, tuning: nil, fingerings: nil, transpose: 0, midi: false|
with_transpose transpose do
with_bpm bpm || current_bpm do
with_fx :reverb do
with_fx :lpf, cutoff: 110 do
with_synth :pluck do
tick :backing_track
notes = chord_notes chords.look(:backing_track), fingerings, tuning
play_strumming notes, pattern.look(:backing_track), midi
end
end
end
end
end
end
live_loop :guitar do
# Uncomment one of the tracks below:
# Simple track
#play_backing_track(chords: ["G","D","C","C"], bpm: 140)
# Nirvana: Smells Like Teen Spirit
#play_backing_track(chords: ["F5","Bb5","Ab5","Db5"], pattern: ["D..UD.xx","xxD.DUDU"], bpm: 160, transpose: -1)
# Iggy Pop: The Passenger
#play_backing_track(chords: ["Am", "F", "C", "G","Am", "F", "C", "E"], pattern: ["x.UD.U"], bpm: 200)
# The Strokes: You Only Live Once
track = {chords: ["Dbm", "E", "B", "F#"],
pattern: [".DDUDUx","UxUxUD.D."],
bpm: 118,
fingerings: {"Dbm" => "xxx999",
"E" => "xxx997",
"B" => "xxx879",
"F#" => "xxx676"}}
#play_backing_track(**track)
# Oasis: Wonderwall
#play_backing_track(chords: ["Em7", "G", "Dsus4", "A7sus4"],
# pattern: ['D.d.D.dU', 'dUD.D.du', 'DUD.D.d', 'U.U.uduDu'] * 5 + ['D.d.D.dU', 'dUD.D.du', 'DUD.D.d', 'U.U.uduD.'],
# bpm: 136)
end