B u s L o g i c:- prolog, code, chatter.

Music Reference Tool in Pure Prolog

I play a bit of guitar and one thing that annoys me is having to figure out scales and chords. There are a lot of different types of scales and each one is different for each musical key. So a little app is required to help with this problem. Initially I wanted it to determine a scale from a root note, but it got a bit more interesting when I added chords and modes as well!

To achieve this, then some relationships have to be defined. There are 12 possible notes and there is a relationship between a note and the next highest note. This is more useful than just specifying a set of facts about notes as you will see.

% note/2 - note(Note, HigherNote).


The important thing to note is the last note (G#/Ab) has a higher note of 'A' which means that this is a circular relationship, this will become important later.

Next Intervals will be defined. An interval is a relationship between two notes, where the notes are separated by a certain range. For example, a Major 3rd interval is three semitones apart.

% interval/3 - interval( Name, RootNote, IntervalNote ).

interval( minor_2nd, Scale, Note )      :- interval_scale_note( 1, Scale, Note ).
interval( major_2nd, Scale, Note )	:- interval_scale_note( 2, Scale, Note ).
interval( minor_3rd, Scale, Note )	:- interval_scale_note( 3, Scale, Note ).
interval( major_3rd, Scale, Note )	:- interval_scale_note( 4, Scale, Note ).
interval( perfect_4th, Scale, Note )	:- interval_scale_note( 5, Scale, Note ).
interval( diminished_5th, Scale, Note )	:- interval_scale_note( 6, Scale, Note ).
interval( perfect_5th, Scale, Note )	:- interval_scale_note( 7, Scale, Note ).
interval( minor_6th, Scale, Note )	:- interval_scale_note( 8, Scale, Note ).
interval( major_6th, Scale, Note )	:- interval_scale_note( 9, Scale, Note ).
interval( minor_7th, Scale, Note )	:- interval_scale_note( 10, Scale, Note ).
interval( major_7th, Scale, Note )      :- interval_scale_note( 11, Scale, Note ).
interval( octave, Scale, Scale ).

The interval/3 predicate makes use of of the interval_scale_note/3 predicate. To achieve this the notes are progressively incremented until the number of notes has passed. For example if you have a 'C' and want an interval of 3 (minor_3rd), then the first iteration will move from 'C' to 'C#/Db', the next to 'D' and the last to 'D#/Eb'.

% interval_scale_note/3 - interval_scale_note( RemainingNotes, RootNote, IntervalNote )

interval_scale_note(0, Note, Note).
interval_scale_note(N, PrevNote, Note) :-
	note(PrevNote, NextNote),
	interval_scale_note(N0, NextNote, Note).

A custom successor is used and defined as such:

% s/2 - successor used for this domain.

s(0,1). s(1,2). s(2,3). s(3,4). s(4,5). s(5,6). s(6,7). s(7,8). s(8,9). s(9,10). s(10,11).

In reality the library predicate succ/2 could be used, but as this is a 'pure' program, I like to remove all references to library predicates if possible. The reason is because the custom s/2 predicate will confine any answers within the domain, which is the values zero to eleven (to cater for 12 note types). This will stop errors if the program tries to go outside that domain.

Just before scales and chords etc.. can be created, one more predicate 'note_list/3' is required. This predicate takes a list of intervals, and a root note and figures out the notes based on the interval from that root note.

% note_list/3 - note_list( IntervalList, RootNote, IntervalNoteList )

note_list( [], _, [] ).
note_list( [N|T], R, [I|It] ) :-
	interval( N, R, I ),
	note_list( T, R, It ).

Alright, so now the code is ready to use. All that is required is a scale definition and then to call the scale. The major scale is defined as so:

% major scale = T-T-S-T-T-T-S

scale( major, Root, Scale ) :-
	note_list( [
	], Root, Scale ).

So now if scale/3 is called with the root note of 'C' the scale resolves to:

?- scale(major, 'C', S).
S = ['C', 'D', 'E', 'F', 'G', 'A', 'B'] ;

And for another root note, like 'F'?

2 ?- scale(major, 'F', S).
S = ['F', 'G', 'A', 'A#/Bb', 'C', 'D', 'E'] ;

The code is fully reversable as well, so if you want to know what a scale is for example, then you can use the following:

4 ?- scale(Type, Key, ['F#/Gb', 'G#/Ab', 'A#/Bb', 'B', 'C#/Db', 'D#/Eb', 'F']).
Type = major,
Key = 'F#/Gb' ;

There could be multiple answers, especially if many different scales have been configured.

Modes are like scales, but a rotation on the root note. So they are easy to configure, and they are in the source for those that are interested.

Chords are just a set of invervals as well, but just not as many as a scale. Here are four types of chord (there are many more), which can be retrieved.

chord( major, Root, Chord) :-
	note_list( [octave, major_3rd, perfect_5th], Root, Chord ).

chord( minor, Root, Chord) :-
	note_list( [octave, minor_3rd, perfect_5th], Root, Chord ).

chord( diminished, Root, Chord) :-
	note_list( [octave, minor_3rd, diminished_5th], Root, Chord ).

chord( augmented, Root, Chord) :-
	note_list( [octave, major_3rd, minor_6th], Root, Chord ).

So to get a A minor chord, just use:

?- chord(minor, 'A', Chord).
Chord = ['A', 'C', 'E'] ;

Now you can derive some chords from a few notes, figure out all chords for a root note and so on.

?- chord( Type, Note, ['A', X, 'E'] ).
Type = major,
Note = 'A',
X = 'C#/Db' ;
Type = minor,
Note = 'A',
X = 'C' ;

?- chord( Type, 'C', Chord ).
Type = major,
Chord = ['C', 'E', 'G'] ;
Type = minor,
Chord = ['C', 'D#/Eb', 'G'] ;
Type = diminished,
Chord = ['C', 'D#/Eb', 'F#/Gb'] ;
Type = augmented,
Chord = ['C', 'E', 'G#/Ab'] ;


This little app, was not much code but is a flexible knowledge base about music. The flexibility comes from the purity of horn clauses, which is hard to get right but once you do, it is slightly magical!