1-score-package-structure


How and why to structure a score project

This is where we talk about how to setup a large score project for getting real-world work done, synthesizing all of our previous information about how Python, Abjad and LilyPond work together.

Key points to keep in mind during this discussion.

  • Python code can be organized into module, packages and subpackages.
  • Python code can import other Python code.
  • Abjad (Python code) can generate LilyPond code.
  • LilyPond code can import other LilyPond code.
  • Settings in LilyPond cascade, much like CSS in web development.
  • LilyPond can concatenate like-named contexts together to create longer musical expressions.
  • LilyPond can extract music expressions using tags, allowing for easy part extraction.
  • A single LilyPond file can generate multiple PDFs as output.
  • LaTeX can combine multiple PDFs into a single PDF.

LilyPond context concatenation

LilyPond can concatenate like-named contexts in adjacent music expressions. That's a mouthful. Let's look at an example:

\score {
    {
        \new Staff = "My Staff" { c'4 d'4 e'4 f'4 }
        \new Staff = "My Staff" { g'4 a'4 b'4 c''4 }
    }
}

The above - as far as LilyPond is concerned - is the equivalent of:

\score {
    {
        \new Staff = "My Staff" { c'4 d'4 e'4 f'4 g'4 a'4 b'4 c''4 }
    }
}

Why? Because the two adjacent Staff expressions both have the same name. Context concatenation is the main reason why we'll be using a ScoreTemplate class to generate the empty score we later fill in with music. By using the same class to generate the score skeleton, we guarantee that every context in the score is named correctly.

Cascading LilyPond settings

When you make settings in a LilyPond file (including any files included at any point in that file) those settings override previous settings. This is somewhat analogous to how CSS works. For example, if you set the title variable in a \header block at the beginning of the file, you can override that title later without losing the other settings in the original header block.

Consider the following:

\header {
    composer = "Best Composer"
    title = "My Great Piece"
    subtitle = "A Very Fine Gigue"
}

\header {
    title = "My Adequate Piece"
}

\header {
    subtitle = "Visions of a Somber World"
}

At the end, the effective header settings are:

\header {
    composer = "Best Composer"
    title = "My Adequate Piece"
    subtitle = "Visions of a Somber World"
}

LilyPond part extraction

We can use LilyPond's \tag and \keepWithTag commands to label parts of our score and then keep only parts with certain labels when rendering a score. If we combine that with LilyPond's \book block functionality, we can use a single LilyPond input file to generate one PDF per musician's part in our score. Easy.

We'll see this in action in the interaction between the ScoreTemplate class, the segments.ily and parts.ily files in the build/ directory, and the parts.ly file in the build/letter-portrait/ directory.

Consider the following LilyPond:

{
    \tag #'foo { c'4 d'4 e'4 f'4 }
    \tag #'bar { g'4 a'4 b'4 c''4 }
}

Let's extract out only the music tagged foo:

\keepWithTag #'foo
{
    \tag #'foo { c'4 d'4 e'4 f'4 }
    \tag #'bar { g'4 a'4 b'4 c''4 }
}

The above is actually equivalent to:

{
    { c'4 d'4 e'4 f'4 }
}

We can use this same principle to extract out the cello part from a string quarter!

Externalized LilyPond typographic overrides: why, again?

Depending on the complexity of the operations you're performing in Python with Abjad to create a score object, it might take a while to generate a chunk of score. If you only need to tweak the length of your stems, why go through that trouble of re-computing the whole score?

Even though Abjad affords you the ability to override typographic settings, sometimes it's easier to just write those settings out - especially if they're global settings - in an external LilyPond file and then include that file.

Then, making global typographic changes is as simple as editing your external stylesheet and re-rendering (not re-computing) the previously rendederd illustration.

The score repository directory

If scores are built from code, let's structure them just like we would structure any other code: as an installable package.

Note: We'll use the command-line tool tree to pretty-print directory structures. The -a flag means "show hidden files". The -F flag causes tree to append special characters like / to indicate directories. The -L 1 flag tells tree to only drill down one level deep. You can install tree on OSX via HomeBrew.


In [2]:
!tree -a -F -L 1 trio_score/


trio_score/
├── .gitignore
├── .travis.yml
├── README.md
├── requirements.txt
├── setup.cfg
├── setup.py
└── trio_score/

1 directory, 6 files

The score package directory

The inner directory holds the actual code and assets that make up a score project.


In [3]:
!tree -a -F -L 1 trio_score/trio_score/


trio_score/trio_score/
├── Makefile
├── __init__.py
├── builds/
├── distribution/
├── etc/
├── materials/
├── segments/
├── stylesheets/
├── test/
└── tools/

8 directories, 2 files

Materials

Materials are objects that exist "outside-of-time". In practice, this generally means configured classes, e.g. rhythm-makers, that you intend to use in various parts of your music. Typically, materials can be illustrated: the material's class implements Abjad's illustration protocol via the __illustrate__() method.


In [4]:
!tree -F -L 1 trio_score/trio_score/materials/


trio_score/trio_score/materials/
├── __init__.py
├── my_droning_rhythm_maker/
├── my_fast_rhythm_maker/
├── my_pitches/
└── my_slow_rhythm_maker/

4 directories, 1 file

In [5]:
!tree -F -L 1 trio_score/trio_score/materials/my_fast_rhythm_maker/


trio_score/trio_score/materials/my_fast_rhythm_maker/
├── __init__.py
├── definition.py
└── illustration.ly

0 directories, 3 files

In [6]:
!cat trio_score/trio_score/materials/my_fast_rhythm_maker/definition.py


# -*- encoding: utf-8 -*-
import os
import abjad
import abjadext.rmakers


my_fast_rhythm_maker = abjadext.rmakers.TaleaRhythmMaker(
    extra_counts_per_division=[0, 0, 1, 0, 1, 2],
    talea=abjadext.rmakers.Talea(
        counts=[1, 1, 2, 3, -1, 4, 1, 1, 2],
        denominator=16,
        ),
    tie_specifier=abjadext.rmakers.TieSpecifier(
        tie_across_divisions=[True, False],
        ),
    )


if __name__ == '__main__':
    illustration_path = os.path.join(
        os.path.dirname(__file__),
        'illustration.pdf',
        )
    abjad.persist(my_fast_rhythm_maker).as_pdf(illustration_path)
    abjad.system.IOManager.open_file(illustration_path)

What's that strange block at the bottom of the material definition.py? The conditional expression if __name__ == 'main': tells Python to execute the following suite if and only if the module is being executed as a script. That means, by running this definition file via python definition.py, we'll illustrate the material defined in the module, render that illustration as a PDF, and then pop open that PDF.

Tools

Tools are any custom classes or functions you use in your score project. Often this is simply a ScoreTemplate and SegmentMaker class.


In [7]:
!tree -F trio_score/trio_score/tools/


trio_score/trio_score/tools/
├── ScoreTemplate.py
├── SegmentMaker.py
└── __init__.py

0 directories, 3 files

In [8]:
!cat trio_score/trio_score/tools/ScoreTemplate.py


# -*- coding: utf-8 -*-
import abjad


class ScoreTemplate(object):

    def __call__(self):

        # Violin
        violin_staff = abjad.Staff(
            [abjad.Voice(name='Violin Voice')],
            name='Violin Staff',
            lilypond_type='ViolinStaff',
            )
        violin_tag = abjad.LilyPondLiteral(r"\tag #'violin", format_slot='before')
        abjad.attach(violin_tag, violin_staff)
        abjad.setting(violin_staff).midi_instrument = abjad.scheme.Scheme(
            'violin', force_quotes=True)

        # Viola
        viola_staff = abjad.Staff(
            [abjad.Voice(name='Viola Voice')],
            name='Viola Staff',
            lilypond_type='ViolaStaff',
            )
        viola_tag = abjad.LilyPondLiteral(r"\tag #'viola", format_slot='before')
        abjad.attach(viola_tag, viola_staff)
        abjad.setting(viola_staff).midi_instrument = abjad.scheme.Scheme(
            'viola', force_quotes=True)

        # Cello
        cello_staff = abjad.Staff(
            [abjad.Voice(name='Cello Voice')],
            name='Cello Staff',
            lilypond_type='CelloStaff',
            )
        cello_tag = abjad.LilyPondLiteral(r"\tag #'cello", format_slot='before')
        abjad.attach(cello_tag, cello_staff)
        abjad.setting(cello_staff).midi_instrument = abjad.scheme.Scheme(
            'cello', force_quotes=True)

        # Everything else
        staff_group = abjad.StaffGroup(
            [violin_staff, viola_staff, cello_staff],
            name='Trio Staff Group',
            )
        score = abjad.Score(
            [staff_group],
            name='Trio Score',
            )

        return score

    def attach(self, score):
        violin_staff = score['Violin Staff']
        viola_staff = score['Viola Staff']
        cello_staff = score['Cello Staff']
        abjad.attach(abjad.Clef('bass'), abjad.select(cello_staff).leaves()[0])
        abjad.attach(abjad.instruments.Cello(), abjad.select(cello_staff).leaves()[0])
        abjad.attach(abjad.Clef('alto'), abjad.select(viola_staff).leaves()[0])
        abjad.attach(abjad.instruments.Viola(), abjad.select(viola_staff).leaves()[0])
        abjad.attach(abjad.Clef('treble'), abjad.select(violin_staff).leaves()[0])
        abjad.attach(abjad.instruments.Violin(), abjad.select(violin_staff).leaves()[0])

In [9]:
!cat trio_score/trio_score/tools/SegmentMaker.py


# -*- coding: utf-8 -*-
import collections
import abjad
from trio_score.tools import ScoreTemplate


class SegmentMaker(object):

    def __init__(
        self,
        time_signatures=None,
        violin_pitches=None,
        violin_rhythm_maker=None,
        violin_seed=None,
        viola_pitches=None,
        viola_rhythm_maker=None,
        viola_seed=None,
        cello_pitches=None,
        cello_rhythm_maker=None,
        cello_seed=None,
        is_last_segment=False,
        ):
        time_signatures = [abjad.TimeSignature(_) for _ in time_signatures]
        assert len(time_signatures)
        self.time_signatures = time_signatures
        self.violin_pitches = violin_pitches
        self.violin_rhythm_maker = violin_rhythm_maker
        self.violin_seed = violin_seed
        self.viola_pitches = viola_pitches
        self.viola_rhythm_maker = viola_rhythm_maker
        self.viola_seed = viola_seed
        self.cello_pitches = cello_pitches
        self.cello_rhythm_maker = cello_rhythm_maker
        self.cello_seed = cello_seed
        self.score_template = ScoreTemplate()
        self.is_last_segment = bool(is_last_segment)

    def __call__(
        self,
        segment_metadata=None,
        previous_segment_metadata=None,
        ):
        score = self.score_template()
        violin_measures = self._make_measures(
            self.time_signatures,
            self.violin_rhythm_maker,
            self.violin_pitches,
            self.violin_seed,
            )
        score['Violin Voice'].extend(violin_measures)
        viola_measures = self._make_measures(
            self.time_signatures,
            self.viola_rhythm_maker,
            self.viola_pitches,
            self.viola_seed,
            )
        score['Viola Voice'].extend(viola_measures)
        cello_measures = self._make_measures(
            self.time_signatures,
            self.cello_rhythm_maker,
            self.cello_pitches,
            self.cello_seed,
            )
        score['Cello Voice'].extend(cello_measures)
        if self.is_last_segment:
            score.add_final_bar_line('|.', to_each_voice=True)
        else:
            score.add_final_bar_line('||', to_each_voice=True)
        self.score_template.attach(score)
        lilypond_file = abjad.LilyPondFile.new(
            score,
            includes=['../../stylesheets/stylesheet.ily'],
            )
        return lilypond_file, segment_metadata

    def _make_measures(self, time_signatures, rhythm_maker, pitches, seed):
        seed = seed or 0
        measures = abjad.Voice()
        for time_signature in time_signatures:
            multimeasure_rest = abjad.MultimeasureRest(1)
            multiplier = abjad.Multiplier(time_signature)
            abjad.attach(multiplier, multimeasure_rest)
            measures.append(abjad.Measure(time_signature, [multimeasure_rest]))
        if rhythm_maker is not None:
            # rotation not available anymore
            divisions = rhythm_maker(time_signatures)
            abjad.mutate(measures).replace_measure_contents(divisions)
        if pitches is not None:
            if not isinstance(pitches, collections.Iterable):
                pitches = [pitches]
            iterator = abjad.iterate(measures).logical_ties(pitched=True)
            iterator = enumerate(iterator, seed)
            for i, logical_tie in iterator:
                pitch = pitches[i % len(pitches)]
                for leaf in logical_tie:
                    leaf.written_pitch = pitch
        return measures

Segments

A segment is a contiguous block of temporal compositional concern. Basically, a chunk of music.

We've established that we can concatenate score together. That means we can concatenate the output of segments together.

But why? There's no reason you have to work with more than one segment. But, with experience, there's only so many details a human can focus on at once. As the contents of your segment become increasingly complicated, you'll have trouble reasoning about what's happening in your segment. If your segment is very long, you may also experience long waits for Abjad and LilyPond to do their work.

By splitting a score into at least a few segments, you both ease your cognitive burden and also speed up the cycle of composing and illustrating.


In [10]:
!tree -F -L 1 trio_score/trio_score/segments/


trio_score/trio_score/segments/
├── __init__.py
├── metadata.json
├── section_one/
├── section_three/
└── section_two/

3 directories, 2 files

In [11]:
!tree -F -L 1 trio_score/trio_score/segments/section_one/


trio_score/trio_score/segments/section_one/
├── __init__.py
├── definition.py
└── illustration.ly

0 directories, 3 files

What's inside a segment definition? A configured instance of the SegmentMaker class from our tools/ directory:


In [12]:
!cat trio_score/trio_score/segments/section_one/definition.py


# -*- encoding: utf-8 -*-
import os
import abjad
from trio_score import materials
from trio_score.tools import SegmentMaker


segment_maker = SegmentMaker(
    time_signatures=[(4, 4), (3, 4), (5, 4)] * 2,
    cello_pitches=[0, 2, 3, 4],
    cello_rhythm_maker=materials.my_droning_rhythm_maker,
    viola_pitches=[0, 2],
    viola_rhythm_maker=materials.my_slow_rhythm_maker,
    violin_pitches=[0, 2],
    violin_rhythm_maker=materials.my_fast_rhythm_maker,
    )


if __name__ == '__main__':
    lilypond_file, _ = segment_maker()
    illustration_path = os.path.join(
        os.path.dirname(__file__),
        'illustration.pdf',
        )
    abjad.persist(lilypond_file).as_pdf(illustration_path)
    abjad.system.IOManager.open_file(illustration_path)

Note that we have the same if __name__ == 'main': block here. The segment definition is illustratable just like the material definitions before.

Stylesheets


In [13]:
!tree -F trio_score/trio_score/stylesheets/


trio_score/trio_score/stylesheets/
├── parts.ily
└── stylesheet.ily

0 directories, 2 files

In [14]:
!cat trio_score/trio_score/stylesheets/stylesheet.ily


#(set-default-paper-size "letter" 'portrait)
#(set-global-staff-size 12)

\header {
    composer = "A Great Composer"
    tagline = ##f
    title = \markup {
        \column {
            \center-align {
                \fontsize #8 {
                    "A Grand Trio"
                }
            }
        }
    }
}

\paper {
    markup-system-spacing = #'(
        (basic-distance . 0)
        (minimum-distance . 0)
        (padding . 8)
        (stretchability . 0)
    )
    system-system-spacing = #'(
        (basic-distance . 12)
        (minimum-distance . 18)
        (padding . 12)
        (stretchability . 20)
    )
}

\layout {
    \context {
        \Staff 
        \name ViolinStaff
        \type Engraver_group
        \alias Staff
        \override NoteHead.color = #blue
        instrumentName = "Violin"
        shortInstrumentName = "Vn."
    }
    \context {
        \Staff 
        \name ViolaStaff
        \type Engraver_group
        \alias Staff
        \override NoteHead.color = #green
        instrumentName = "Viola"
        shortInstrumentName = "Va."
    }
    \context {
        \Staff 
        \name CelloStaff
        \type Engraver_group
        \alias Staff
        \override NoteHead.color = #red
        instrumentName = "Cello"
        shortInstrumentName = "Yo yo yo yo"
    }
    \context {
        \StaffGroup
        \accepts ViolinStaff
        \accepts ViolaStaff
        \accepts CelloStaff
        \override SpacingSpanner.base-shortest-duration = #(ly:make-moment 1 16)
        \override SpacingSpanner.uniform-stretching = ##t
        \override StaffGrouper.staff-staff-spacing = #'(
            (basic-distance . 10)
            (minimum-distance . 15)
            (padding . 5)
            (stretchability . 100)
            )
        \override TimeSignature.style = #'numbered
        autoBeaming = ##f
        proportionalNotationDuration = #(ly:make-moment 1 16)
        tupletFullLength = ##t
    }
}

In [15]:
!cat trio_score/trio_score/stylesheets/parts.ily


\paper {
}

\layout {
}

The build directory

The build/ directory is where we put our document together: the front and back covers, the guts of the score, performance notes and parts.

This area relies on more complex include logic than we've seen before, but nothing too complicated.


In [17]:
!tree -F trio_score/trio_score/builds/


trio_score/trio_score/builds/
├── assets/
│   ├── instrumentation.tex
│   └── performance-notes.tex
├── letter-portrait/
│   ├── Makefile
│   ├── back-cover.pdf
│   ├── back-cover.tex
│   ├── front-cover.pdf
│   ├── front-cover.tex
│   ├── music.ly
│   ├── music.pdf
│   ├── parts-cello.pdf
│   ├── parts-viola.pdf
│   ├── parts-violin.pdf
│   ├── parts.ly
│   ├── preface.pdf
│   ├── preface.tex
│   ├── score.pdf
│   └── score.tex
├── parts.ily
├── segments/
│   ├── section-one.ily
│   ├── section-three.ily
│   └── section-two.ily
└── segments.ily

3 directories, 22 files

In [18]:
!cat trio_score/trio_score/builds/segments.ily


{
    \include "../segments/section-one.ily"
    \include "../segments/section-two.ily"
    \include "../segments/section-three.ily"
}

In [19]:
!cat trio_score/trio_score/builds/parts.ily


\book {
    \bookOutputSuffix "violin"
    \header {
        subtitle = "Violin Part"
    }
    \score {
        \keepWithTag #'(violin)
        \include "../segments.ily"
    }
}

\book {
    \bookOutputSuffix "viola"
    \header {
        subtitle = "Viola Part"
    }
    \score {
        \keepWithTag #'(viola)
        \include "../segments.ily"
    }
}

\book {
    \bookOutputSuffix "cello"
    \header {
        subtitle = "Cello Part"
    }
    \score {
        \keepWithTag #'(cello)
        \include "../segments.ily"
    }
}

Anatomy of a build target


In [21]:
!tree -F trio_score/trio_score/builds/letter-portrait/


trio_score/trio_score/builds/letter-portrait/
├── Makefile
├── back-cover.pdf
├── back-cover.tex
├── front-cover.pdf
├── front-cover.tex
├── music.ly
├── music.pdf
├── parts-cello.pdf
├── parts-viola.pdf
├── parts-violin.pdf
├── parts.ly
├── preface.pdf
├── preface.tex
├── score.pdf
└── score.tex

0 directories, 15 files

Other directories

There are three other directories in our score project: distribution/, etc/ and test/.

We use etc/ to keep any notes or plans about the piece as we're writing it.

We use test/ to house any tests for our score - typically just illustrating every material and segment in the project and checking that nothing broke during the process.

Finally, we use distribution/ for housing the PDFs that are ready to be zipped-up and mailed out to any performer or ensemble who wants to play our music.


In [22]:
!tree -F trio_score/trio_score/distribution/


trio_score/trio_score/distribution/

0 directories, 0 files

In [23]:
!tree -F trio_score/trio_score/etc/


trio_score/trio_score/etc/

0 directories, 0 files

In [24]:
!tree -F trio_score/trio_score/test/


trio_score/trio_score/test/
├── test_materials.py
└── test_segments.py

0 directories, 2 files

Putting it all together


In [25]:
!tree -F trio_score/


trio_score/
├── README.md
├── requirements.txt
├── setup.cfg
├── setup.py
└── trio_score/
    ├── Makefile
    ├── __init__.py
    ├── builds/
    │   ├── assets/
    │   │   ├── instrumentation.tex
    │   │   └── performance-notes.tex
    │   ├── letter-portrait/
    │   │   ├── Makefile
    │   │   ├── back-cover.pdf
    │   │   ├── back-cover.tex
    │   │   ├── front-cover.pdf
    │   │   ├── front-cover.tex
    │   │   ├── music.ly
    │   │   ├── music.pdf
    │   │   ├── parts-cello.pdf
    │   │   ├── parts-viola.pdf
    │   │   ├── parts-violin.pdf
    │   │   ├── parts.ly
    │   │   ├── preface.pdf
    │   │   ├── preface.tex
    │   │   ├── score.pdf
    │   │   └── score.tex
    │   ├── parts.ily
    │   ├── segments/
    │   │   ├── section-one.ily
    │   │   ├── section-three.ily
    │   │   └── section-two.ily
    │   └── segments.ily
    ├── distribution/
    ├── etc/
    ├── materials/
    │   ├── __init__.py
    │   ├── my_droning_rhythm_maker/
    │   │   ├── __init__.py
    │   │   ├── definition.py
    │   │   └── illustration.ly
    │   ├── my_fast_rhythm_maker/
    │   │   ├── __init__.py
    │   │   ├── definition.py
    │   │   └── illustration.ly
    │   ├── my_pitches/
    │   │   ├── __init__.py
    │   │   ├── definition.py
    │   │   └── illustration.ly
    │   └── my_slow_rhythm_maker/
    │       ├── __init__.py
    │       ├── definition.py
    │       └── illustration.ly
    ├── segments/
    │   ├── __init__.py
    │   ├── metadata.json
    │   ├── section_one/
    │   │   ├── __init__.py
    │   │   ├── definition.py
    │   │   └── illustration.ly
    │   ├── section_three/
    │   │   ├── __init__.py
    │   │   ├── definition.py
    │   │   └── illustration.ly
    │   └── section_two/
    │       ├── __init__.py
    │       ├── definition.py
    │       └── illustration.ly
    ├── stylesheets/
    │   ├── parts.ily
    │   └── stylesheet.ily
    ├── test/
    │   ├── test_materials.py
    │   └── test_segments.py
    └── tools/
        ├── ScoreTemplate.py
        ├── SegmentMaker.py
        └── __init__.py

19 directories, 59 files

In [ ]: