Automatic Generation of Scales in Various Modes for All 21 Pitches

Ever wondered, after reading the theory of scales and modes, what a G♯ locrian scale looks like? With LilyPond you can automatically produce whole sets of scales in all modes for any key. There has been discussion about programmatically generating LilyPond scores with Scheme (LilyPond’s inline extension language) and Python. Today I’ll show you that you can also use plain old shell programming to generate a complete set of scales.

For this we will

    1. Create LilyPond templates for the scales and scores
    2. Create a script that generates compilable LilyPond input files for a given (set of) key(s)
    3. Create a script that automates this process up to the completed PDF scores

 

Common scales

Let’s start with definitions of the eleven common scales from which we’ll later build our scores, such as the following (saved in a file named BasicScales.ly). We’ll display them in both ascending and descending order, since some notes may differ in the latter order. For example, we have:

CMajorScale = {
  c d e f g a b c
  c b a g f e d c
}

CMelodicMinorScale = {
  c d ees? f g a! b! c
  c bes? aes! g f ees? d c
}

CBluesScale = {
  % Only 6 notes
  c ees? f ges? g bes c s
  s c bes g ges? f ees? c
}

The ? and ! are LilyPond commands for printing a cautionary accidental and forcing an non-parenthesized accidental to appear, respectively. They emphasize differences between scales with the same tonic.

The so-called “greek” modes

The modes are easily built from the white keys of the piano, for example:

CIonianScale = {
  c d e f g a b c
  c b a g f e d c
}

DDorianScale = {
  d e f g a b c d
  d c b a g f e d
}

GMixolydianScale = {
  g a b c d e f g
  g f e d c b a g
}

(You can see the remaining modes – phrygian, lydian, eolian, and locrian – in the BasicScales.ly file.)

Score templates

To have our scales formatted nicely we need to place some more LilyPond code around them, and save these templates in files named *ScalesTemplate.ly.  For example, BassScalesTemplate.ly (click here to see formatted source in a pdf) and TrebleAndBassScalesTemplate.ly (formatted pdf here).

The templates contain a staff definition like the following:

__PITCH_FILENAME_FRAGMENT__HarmonicMinor = {
  \new Staff {
    \clef bass
    \key __LILY_PITCH__ \minor
    %    Hide time signature
    \override Staff.TimeSignature #'stencil = ##f
    \time 8/4
    \myTempoMark #2.0 #"Mineur harmonique"
    \transpose c __LILY_PITCH__ 
    \relative __BASS_REF__ {
      \CHarmonicMinorScale
    }
  }
}

The placeholders (__PITCH_FILENAME_FRAGMENT____LILY_PITCH__, etc.) make the code generic.  They aren’t valid LilyPond code but have to be replaced by real values. This will be done programmatically to generate the actual LilyPond code used to produce the actual scores. __LILY_PITCH__ will thus be replaced by aes, a, and the like, once for each pitch.

The tempo mark such as Mineur harmonique can be adapted at will.  It is just constant text that will be included in the generated LilyPond files.

Since LilyPond’s \transpose takes care of everything regarding alterations, one doesn’t have to bother with counting semitones.

We use the time signature 8/4 so that one passage will take one bar, but we don’t actually want it displayed, so we use \override Staff.TimeSignature #'stencil = ##f.

The script will handle all of the *ScalesTemplate.ly files in the current directory.

Generating scores from the templates

This is done with a bash shell script, i.e. a text file whose contents are bash commands.

GenerateScales.sh can be given a single pitch as an argument, or it will produce scales for all 21 pitches:

menu@mymac > ./GenerateScales.sh -h

-> Usage: ./GenerateScales.sh [-h(elp)] [pitch]

    'pitch', if present, must be one of:
        aes a ais bes b bis ces c cis des d dis ees d dis fes f fis ges g gis
    Otherwise, all 21 pitches are handled

The global structure of the script (in pseudo code) is:

for LILY_PITCH in ${PITCHES}; do
   define variables
   for TEMPLATE_FILE in $(ls *ScalesTemplate.ly); do
      handle template file
   done
done

Variables used

The value of the variables is determined as follows, based on the pitches names:

# Following *_REF are to adjust scale height for it to be easily readable
# First setting default values
TREBLE_REF="c'"
BASS_REF="c,"

# Use the following if you want all upper case letters
# ENGLISH_PITCH_FILENAME_FRAGMENT=$(echo ${LILY_PITCH} | tr a-z A-Z)
# Otherwise keep definition below
ENGLISH_PITCH_FILENAME_FRAGMENT=${LILY_PITCH}

# Analyze pitch to define useful variables,
# including *_REF if needed to obtain easily readable notes
case ${LILY_PITCH} in
aes )
FRENCH_PITCH_FILENAME_FRAGMENT="LaBemol"
FRENCH_PITCH_NAME="La bémol"
;;

...
esac

How placeholders are replaced with actual values

This is done with sed, which is part of the standard UNIX/Linux/MacOS X toolbox. sed is a tool that can edit (or rather process) text that is streamed through it (as opposed to manual editing). For example,

sed "s/__LILY_PITCH__/${LILY_PITCH}/g"

replaces all occurrences of __LILY_PITCH__ with the value of variable LILY_PITCH.

This is done in a sequence of so-called pipes. In a pipe (short for pipeline, and written “|”), the output of a command is fed into the input of the next one. The sequence starts with cat, which passes the contents of the template file (the name of which is stored in the TEMPLATE_FILE variable) to the chain of sed commands.

The output of the pipe is then stored in a file (the name of this file is taken from ENGLISH_LILY_FILENAME variable) through redirection (“>“):

      cat ${TEMPLATE_FILE} | \
         sed "s/__LILY_PITCH__/${LILY_PITCH}/g" | \
         sed "s/__FRENCH_PITCH_NAME__/${FRENCH_PITCH_NAME}/g" | \
         sed "s/__PITCH_FILENAME_FRAGMENT__/${ENGLISH_PITCH_FILENAME_FRAGMENT}/g" | \
         sed "s/__TREBLE_REF__/${TREBLE_REF}/g" | \
         sed "s/__BASS_REF__/${BASS_REF}/g" \
         > \
         ${ENGLISH_LILY_FILENAME}

Scores production

Now we tell Lilypond to compile the generated .ly file with the following command:

lilypond ${ENGLISH_LILY_FILENAME}

We then create sibling files with French names for the convenience of French-speaking users:

cp -p ${ENGLISH_LILY_FILENAME} ${FRENCH_LILY_FILENAME}
cp -p ${ENGLISH_PDF_FILENAME} ${FRENCH_PDF_FILENAME}

The language of the text fragments in the scores that are produced is the same language that is used in the templates. (This can be changed if desired.)

GenerateScales.sh in practice

menu@mymac > ./GenerateScales.sh gis
--> LILY_PITCH                      = gis
--> TREBLE_REF                      = c
--> BASS_REF                        = c,
--> ENGLISH_PITCH_FILENAME_FRAGMENT = gis
--> ENGLISH_PITCH_NAME              = gis
--> FRENCH_PITCH_FILENAME_FRAGMENT  = SolDiese
--> FRENCH_PITCH_NAME                = Sol dièse

--> TEMPLATE_NAME                   = Bass

--> ENGLISH_LILY_FILENAME           = gis_Bass.ly
--> FRENCH_LILY_FILENAME            = SolDiese_Bass.ly

--> ENGLISH_PDF_FILENAME            = gis_Bass.pdf
--> FRENCH_PDF_FILENAME             = SolDiese_Bass.pdf

GNU LilyPond 2.17.20
Processing 'gis_Bass.ly'
Parsing...
Interpreting music...
Preprocessing graphical objects...

...

Finding the ideal number of pages...
Fitting music on 1 or 2 pages...
Drawing systems...
Layout output to 'gis_Bass.ps'...
Converting to './gis_Bass.pdf'...
Success: compilation successfully completed

16  -rw-r--r--+ 1 menu  admin  5626 Aug 15 17:09 gis_Bass.ly
192 -rw-r--r--+ 1 menu  admin  94801 Aug 15 17:09 gis_Bass.pdf
16  -rw-r--r--+ 1 menu  admin  5626 Aug 15 17:09 SolDiese_Bass.ly
192 -rw-r--r--+ 1 menu  admin  94801 Aug 15 17:09 SolDiese_Bass.pdf

You can get those files in this GenerateScales.tar.gz archive.
In particular, gis_TrebleAndBass.pdf contains the G# locrian scale we promised, both in treble and bass keys:

gis_TrebleAndBass_Locrian

Conclusion

The power of Lilypond, namely her ability to process text input files that contain instructions describing a score to be printed, integrates seamlessly with the power of simple programming tools such as shell scripts.  For example, if you mistakenly introduce some error in a scale (as happened with me in the case of a minor harmonic scale), you just fix the scale “definition”, re-run the script and all the variations are corrected automatically!

As always with programming, this opens a wealth of opportunities.  You’re only limited by your inventiveness.  We leave it to you as an exercise to download the archive and play with it, for example, creating scales in alto or tenor clefs.

Enjoy!

4 thoughts on “Automatic Generation of Scales in Various Modes for All 21 Pitches

  1. Adam Spiers

    That’s funny that you just posted this around the same that I announced my LilyPond-based Scale Matcher website:

    http://blog.adamspiers.org/2013/08/23/announcing-the-scale-matcher/

    To build that, I wrote a set of Ruby libraries which understand about notes, intervals, scale types, modes, keys etc., e.g.

    [1] pry(main)> require 'mode'
    => true
    [2] pry(main)> notes = Mode.new(7, DiatonicScaleType::MELODIC_MINOR).notes_from(Note["G#"])
    => [G#4, A4, B4, C5, D5, E5, F#5]
    [3] pry(main)> notes.to_ly_abs
    => "gs' a' b' c'' d'' e'' fs''"

    and then built a Rails app on top of it which dynamically generates (and caches) LilyPond files and rendered PNGs. The libraries have a comprehensive test suite and I was thinking about publishing them as a gem at some point.

    Reply
  2. Jacques Menu Post author

    Hello Adam,

    What you did is much more ambitious and dynamic, since it’s aiming at practical improvisation, while my approach is more academic.

    Great job, congratulations!

    JM

    Reply
  3. Marc

    why not including the E scales ?

    # The Lilypond pitches
    ALL_PITCHES="aes a ais bes b bis ces c cis des d dis ees d dis fes f fis ges g gis"

    should really be

    ALL_PITCHES="
    c       
    cis des  
    d      
    dis  es  
    e     fes  
    eis  f
    fis   ges
    g    
    gis  aes  
    a   
    ais  bes  
    b   
    bis ces
    Reply
    1. Jacques Menu

      Hello Marc,

      You’re right, that bug was looked over… Thanks for the fix, and a happy new year!

      JM

      Reply

Leave a Reply

Your email address will not be published. Required fields are marked *