Skip to content

Rethinking Points and Sequences

jtauber edited this page Apr 14, 2013 · 1 revision

Rethinking Points and Sequences

Introduction

For a long time (Nicholas Tollervey will remember) I've known we wanted this notion of a set of properties that applied to an entire sequence.

Prior to PyCon, I had an idea of how we'd do it but during the sprints, I changed my mind after discussions with Brant Harris and we basically inverted my originally-intended approach.

The last couple of weeks, I've been thinking about taking it a step further. It's quite a radical change, so one that warrants some design discussion (and probably work on a branch for while)

Quick Primer on Sequences and Points

Recall that Sebastian has three types of sequences. Each sequence is a list-like data structure but they differ in whether the items in the list are considered horizontal (the next one starts when the previous one ends), vertical (they are simultaneous) or offset-based (each item has an offset that tells you when it's played).

Each sequence type is a different Python class. This has the advantage that you can do things like A + B with horizontal sequences and C // D with vertical sequences. It does have the disadvantage that you can't underspecify for sequence type. You can't have a list of items where you don't care (or know yet) whether they are horizontal or vertical.

The items in a sequence are points. A point is a dictionary-like data structure of key-value pairs. The use of dictionaries allows both arbitrary properties to be added to a point and also underspecification (e.g. I know this is the 2nd degree of the scale but I don't know the key so I don't know the actual letter name yet).

The Original Idea: "Head Points"

I knew a long time ago that I wanted to be able to specify certain properties for an entire sequence. An obvious example is specifying the key, or tempo. It doesn't really make sense to specify the tempo on each note in a sequence, say.

Also, there's a lot of power in annotating subsequences. Consider the opening of Mozart's C major sonata K545 where the left hand plays an Alberti base in the chords I I V7c I IVc I Vb I. This could be modeled as a sequence of sequences (each subsequence a particular chord). The subsequences would contain the individual notes being played but each subsequence could be annotated with a key-value pair indicating that it's the V7c chord, etc.

So my thinking (never implemented) was to think of a sequence not just as a list but as a pair of a dictionary (or more precisely, a Point) and a list. I called the sequence's overall dictionary the "head point".

So in short, instead of

Sequence = [Point, Point, Point]

we have

Sequence = (HeadPoint, [Point, Point, Point])

The PyCon Sprints Idea: "Points Having a Sequence"

Discussions with Brant Harris convinced me we could flip things around and instead of

Sequence = (HeadPoint, [Point, Point, Point])

we could do

Point = {property1: value1, property2: value2, children: Sequence}

(where for ease-of-reading I've just used dictionaries for points)

In other words, the nodes in the tree would not be Sequences containing a head point and a list of children, but the nodes would just be Points and a Point can have a property "children" that is a sequence.

In short: instead of a sequence having a head point, a sequence has a surrounding point.

I preferred this approach and Brant did an initial implementation which made use of in his first pass at the K545 opening Alberti.

As a side note, I like the notion, again conceived by Brant, of effectively having lazy expansion of children. You could have a point {key: "C", chord: "IVc", figuration: "alberti"} and only expand that into its child HSeq when needed (an important point being you don't replace this with a sequence, you just expand it by adding a children property).

When I started working on the Main Title from Game of Thrones, it became clear that for Lilypond rendering to work, I needed to indicate the piece is in 3/4. This new approach of "Points Having a Sequence" is perfect for that:

Point = {time_signature: (3,4), children: Sequence}

The first sequence could just be a sequence of Points defining the sections of a piece. I can image, for example:

{title: "Sonata in C, K 545", key: "C", children: [
    {title: "First Movement", relative_key: "tonic", time_signature: "common", children: [...]},
    ....
]}

(for ease-of-reading I've used dictionaries for points and lists for sequences)

But starting to play with this got me thinking further...

My Current Thinking: "Get Rid of Sequence as a Python Class"

Remember at the start I said a disadvantage of using Python Classes distinguishing HSeq from VSeq from OSeq is you can't then underspecify for that type? Well what if the sequence type was just a property on the surrounding Point.

For example, instead of

{children: HSeq([...])}

we have

{sequence_type: "horizontal", children: [...]}

Underspecification just works then by not providing a "sequence_type" property on the Point.

However, we've, at least initially, lost the wonderful operating overloading to allow things like A + B, C // D, E * 4.

I think that's solvable though, and that's I think the main design discussion I want to see out of this post.

I think we just need to decide what +, // and * (probably as well as |) might mean if we move those operators to apply to points containing sequences rather than just points.

Thoughts? Concerns?

I think the only real concerns I have are (1) it will make most of my PyCon talk wrong (easy to fix, I can just record an updated version of the 10 minutes on Sebastian; I probably already need to do that); (2) implementing it will affect everything so stuff will be broken in the interim (we can do it on a branch, which we probably should anyway, but it means master is instantly deprecated).