Posted: Apr 28, 2016 6:25 pm
by John Platko
As I was "composing" the song I discussed in my last comment I became aware that something important was missing. A blues lead isn't just lick, lick, lick, :nono: no matter how good the licks may be it gets to be a bit too much if you don't break them up from time to time by contrasting them with some other tickling of notes up and down the length of the neck. Playing scales or arpeggios to connect licks is often used to do this. They can be especially effective if licks are separated by some distance on the fretboard. In that case, a scale can be used to connect the last note of one lick to the first note of the next lick. So I decided I needed a way to tell lickmaker to connect two licks in a song with an appropriate scale or arpeggio. These are often called "runs".

There are several problems that need to be solved to do this.

1) You need to find the notes of a given scale that are between the notes you are trying to connect with the scale.

2) You don't want to only be able to use those notes in the natural order they fall between the scale- that tends to sound too much like you're just playing a scale - so different patterns of playing notes, in different order, sometimes repeating a note are needed.

3) You need to be able to expand or contract the set of notes selected so you end up with an appropriate number of notes for the amount of time you want them to play in- for now, I'm talking one bar worth of notes.

4) You need to be able to pick a rhythm pattern to play the scale or arpeggio with. You don't want to always play machine gun like 16 notes. You want different rhythm patterns to be available- for blues, some shuffle patterns often work well.

5) There's a connection between the rhythm pattern selected and the number of notes you need so 4) effects 3)

Now all of those are interesting problems and I'd be happy to talk more about any or all of them if anyone is interested but the problem I found most interesting in all of this is:

6) After the desired notes are selected you have to decide where to play them because a guitar, unlike a piano, often has multiple places where the same note can be played. So, if I want lickmaker to compose some scale run between two points it needs to come up with a reasonable way to play those notes. Unreasonable would be bouncing back and forth between the high and low end of the fretboard on alternate notes of the run- you want a more smooth transition of notes. You also don't want to just bounce up one string playing all the notes because that doesn't make best use of all your fingers.

Some example might be helpful at this point.

Let's say you want lickmaker to compose a minor blues scale run from e, (that the lowest e note on a guitar in lilypond speak) to e'' (e two octaves up from e,). Now guitar players typically know what patterns are available on a fretboard to play these notes but the general problem of how to choose where to play each note depends on a lot of different things and in the end amounts to personal choice. Let's say you want to play these notes:

['e,', 'g,', 'a,', 'bes,', 'ces', 'd', 'e', 'g', 'a', 'bes', 'b', "d'", "e'"]

There is only one place to play the "e," and one place to play the "g," but lots of places to play the "b".

Below, next to each note is a list containing a list showing the possibilities of where each note could be played. e.g. the e, note can only be played on the 6th string with an open fret. the g, can only be played on the 6th string at the 3rd fret. But there's a lot of places where the b note can be played.

e, [[6, 0]]
g, [[6, 3]]
a, [[5, 0], [6, 5]]
bes, [[5, 1], [6, 6]]
ces [[5, 2], [6, 7]]
d [[4, 0], [5, 5], [6, 10]]
e [[4, 2], [5, 7], [6, 12]]
g [[3, 0], [4, 5], [5, 10], [6, 15]]
a [[3, 2], [4, 7], [5, 12], [6, 17]]
bes [[3, 3], [4, 8], [5, 13], [6, 18]]
b [[2, 0], [3, 4], [4, 9], [5, 14], [6, 19]]
d' [[2, 3], [3, 7], [4, 12], [5, 17]]
e' [[1, 0], [2, 5], [3, 9], [4, 14], [5, 19]]

This problem had me :scratch: for a while because I wanted to solve it in a general way that would work for all sorts of scales and arpeggios and it's kind of tricky because there is no one solution. The best choice of which notes to play depends on all sorts of things- for a run - where you're starting from and where you want to end up is important- but other things matter too.

After thinking about it for a while I realized that this is essentially a special case of the traveling salesman problem. i.e. how to schedule a route to x cities traveling the shortest distance. This is a bit different but the concept is similar.

One solution that is often desired by guitar players is to play the notes as much across the neck as possible. That is, keep the notes in the smallest lengthwise area of fretboard as possible. If that's what you want then you can often eliminate a lot of the note position choices and make the overall problem easier- sometimes the solution pops out of a fairly simple search like that. But not always.

Rather than complicate this comment with how that sort of search works I'll just move on to the general case and show one way, a heuristic way, of solving it in all? cases. In keeping with the spirit of the thread, I'm using a genetic heuristic to solve it.

This idea is to come up with a set of metrics that describe favorable or unfavorable solutions. For example, more that 3 notes on a given string might be unfavorable - so that gets penalized. An overall short distance between notes is a favorable metric. Playing open strings may or may not be favorable- you want to be able to select for that.

With metric in place, lickmaker just randomly picks a bunch of solutions from the possibility space. Then it computes how well each solution did. It keeps the best 50% and tosses the others. Then is uses the best solutions to randomly crate a new set of possible solutions to take the place of those that were tossed. Again it keeps the best 50% from the new and old solutions, and keeps repeating this until a stable best solution is found. So for our example:

e, [[6, 0]]
g, [[6, 3]]
a, [[5, 0], [6, 5]]
bes, [[5, 1], [6, 6]]
ces [[5, 2], [6, 7]]
d [[4, 0], [5, 5], [6, 10]]
e [[4, 2], [5, 7], [6, 12]]
g [[3, 0], [4, 5], [5, 10], [6, 15]]
a [[3, 2], [4, 7], [5, 12], [6, 17]]
bes [[3, 3], [4, 8], [5, 13], [6, 18]]
b [[2, 0], [3, 4], [4, 9], [5, 14], [6, 19]]
d' [[2, 3], [3, 7], [4, 12], [5, 17]]
e' [[1, 0], [2, 5], [3, 9], [4, 14], [5, 19]]

It came up with:

0 e, [6, 0]
1 g, [6, 3]
2 a, [6, 5]
3 bes, [5, 1]
4 ces [5, 2]
5 d [4, 0]
6 e [4, 2]
7 g [4, 5]
8 a [3, 2]
9 bes [3, 3]
10 b [3, 4]
11 d' [2, 3]
12 e' [2, 5]

Given some different priorities, Lickmaker came up with a perhaps more conventional choice:

0 e, [6, 0]
1 g, [6, 3]
2 a, [5, 0]
3 bes, [5, 1]
4 ces [5, 2]
5 d [4, 0]
6 e [4, 2]
7 g [3, 0]
8 a [3, 2]
9 bes [3, 3]
10 b [2, 0]
11 d' [2, 3]
12 e' [1, 0]



And if we go up another octave on our run using this set of notes;

['e,', 'a,', 'ces', 'e', 'g', 'a', 'bes', 'b', "d'", "e'", "g'", "a'", "bes'", "ces''", "d''", "e''"]

These are the possibilities of where each note can be played:

e, [[6, 0]]
a, [[5, 0], [6, 5]]
ces [[5, 2], [6, 7]]
e [[4, 2], [5, 7], [6, 12]]
g [[3, 0], [4, 5], [5, 10], [6, 15]]
a [[3, 2], [4, 7], [5, 12], [6, 17]]
bes [[3, 3], [4, 8], [5, 13], [6, 18]]
b [[2, 0], [3, 4], [4, 9], [5, 14], [6, 19]]
d' [[2, 3], [3, 7], [4, 12], [5, 17]]
e' [[1, 0], [2, 5], [3, 9], [4, 14], [5, 19]]
g' [[1, 3], [2, 8], [3, 12], [4, 17]]
a' [[1, 5], [2, 10], [3, 14], [4, 19]]
bes' [[1, 6], [2, 11], [3, 15], [4, 20]]
ces'' [[1, 7], [2, 12], [3, 16]]
d'' [[1, 10], [2, 15], [3, 19]]
e'' [[1, 12], [2, 17]]

And this is the solution lickmaker came up with:

0 e, [6, 0]
1 a, [6, 5]
2 ces [6, 7]
3 e [5, 7]
4 g [5, 10]
5 a [4, 7]
6 bes [4, 8]
7 b [4, 9]
8 d' [3, 7]
9 e' [3, 9]
10 g' [3, 12]
11 a' [2, 10]
12 bes' [2, 11]
13 ces'' [2, 12]
14 d'' [1, 10]
15 e'' [1, 12]

Now there's a bit more to it that this, there are other options available, other optimizations you could deem important but perhaps this is complicated enough for an introduction to the topic. It's a bit of AI technology buried in lickmaker to help it create sensible runs to play.