music21.humdrum.spineParser

music21.humdrum.spineParser is a collection of utilities for changing native Humdrum code into music21 streams. Most music21 users will simply want to call:

>>> myFile = converter.parse('myFile.krn')
>>> myFile.show()

The methods in here are documented for developers wishing to expand music21’s ability to parse Humdrum.

SpineParsing consists of several steps.

  • The data file is read in and all events are sliced horizontally (EventCollections)

    and vertically (Protospines)

  • Protospines are parsed into HumdrumSpines by following Spine Path Indicators

    (*^ and *v especially) Protospines that separate become new Protospines with their parentSpine indicated. Protospines that merge again are then followed by the same Protospine as before. This will cause problems if a voice re-merges with another staff, but in practice I have not seen a .krn file that does this, and it should be avoided in any case.

  • HumdrumSpines are reclassed according to their exclusive definition.

    **kern becomes KernSpines, etc.

  • All reclassed HumdrumSpines are filled with music21 objects in their .stream property.

    Measures are put into the spine but are empty containers. The resulting HumdrumSpine.stream objects look like Stream.flatten(retainContainers=True) versions in many ways.

  • For HumdrumSpines with parent spines their .stream contents are then

    inserted into their parent spines with voice tagged as a music21 Group property.

  • Lyrics and Dynamics are placed into their corresponding HumdrumSpine.stream objects.

  • Stream elements are moved into their measures within a Stream.

  • Measures are searched for elements with voice groups and Voice objects are created.

  • Changed in v10: flag attributes isSpineLine, isComment, isGlobal, isReference have been

    removed from HumdrumLine and all subclasses. isinstance(...) is a bit slower than checking these, but adds correct typing. .iterIndex and hand rolled iterator/generators are gone now (they could’ve gone away when Python 2.4 became the minimum version). HumdrumDataCollection.parsePositionInStream is gone (was duplicating numLines); HumdrumDataCollection.fileLength is now the read-only property numLines (= len(self.dataStream)); HumdrumLine.position and SpineEvent.position were both renamed to lineNumber and now hold the 1-based source line number (was eventList index for SpineEvent), advancing on blank lines too. Blank lines now produce BlankLine HumdrumLine entries rather than being skipped, in order that eventList[lineNumber - 1] always resolve to the line at that source position.

BlankLine

class music21.humdrum.spineParser.BlankLine(lineNumber: int = 0)[source]

A blank line in a Humdrum source. Humdrum forbids blank lines, but they appear in the wild. Storing them as BlankLine objects keeps eventList aligned with the source: eventList[lineNumber - 1] is always the HumdrumLine at that 1-based source line.

Treated as a global event by parseProtoSpinesAndEventCollections (no SpineEvent is created at this line for any spine).

>>> b = humdrum.spineParser.BlankLine(lineNumber=12)
>>> b
<music21.humdrum.spineParser.BlankLine line 12>
>>> b.lineNumber
12

BlankLine bases

BlankLine read-only properties

Read-only properties inherited from ProtoM21Object:

DynamSpine

class music21.humdrum.spineParser.DynamSpine(spineId: int = 0, eventList: list[SpineEvent] | None = None, streamClass: type[Stream] = <class 'music21.stream.base.Stream'>, parentSpineCollection: SpineCollection | None = None)[source]

A DynamSpine is a type of Humdrum spine with the **dynam attribute set and thus events are processed as if they are dynamics.

DynamSpine bases

DynamSpine read-only properties

Read-only properties inherited from ProtoM21Object:

DynamSpine read/write properties

Read/write properties inherited from HumdrumSpine:

DynamSpine methods

DynamSpine.parse() → None[source]

Dummy method that pushes all these objects to HumdrumSpine.stream as ElementWrappers. Should be overridden in specific Spine subclasses.

Methods inherited from HumdrumSpine:

EventCollection

class music21.humdrum.spineParser.EventCollection(maxSpines: int = 0, lineNumber: int = 0)[source]

An EventCollection holds every event that appears on a single source line of a Humdrum file – one cell per spine, in their events array.

If a spine’s cell on this line is ‘.’ or a comment (no new event), use lastEvents[spineNum] to retrieve the previous event still sounding. getSpineOccurring returns either the current event or the last event that’s still active.

These events should happen at the same time (offset) but they are not necessarily the only events to happen at that same time. E.g. a clef change and the note that follows it on the next line take place at the same musical time but live in different EventCollections.

These objects normally get created by parseProtoSpinesAndEventCollections() so you won’t need to do all the setup.

Assume that ec1 is

C4 pp

on source line 4, and ec2 is

D4 .

on source line 5:

>>> SE = humdrum.spineParser.SpineEvent
>>> eventList1 = [SE('C4', lineNumber=4), SE('pp', lineNumber=4)]
>>> eventList2 = [SE('D4', lineNumber=5), SE('.', lineNumber=5)]
>>> ec1 = humdrum.spineParser.EventCollection(maxSpines=2, lineNumber=4)
>>> ec1.events = eventList1
>>> ec2 = humdrum.spineParser.EventCollection(maxSpines=2, lineNumber=5)
>>> ec2.events = eventList2
>>> ec2.lastEvents[1] = eventList1[1]
>>> ec2.maxSpines
2
>>> ec2.lineNumber
5
>>> ec2.getAllOccurring()
[<music21.humdrum.spineParser.SpineEvent D4>, <music21.humdrum.spineParser.SpineEvent pp>]

If there were spine path markers this next boolean would be true:

>>> ec2.spinePathData
False

In the future, EventCollection may enforce that all events have the same lineNumber.

  • New in v10: lineNumber.

EventCollection bases

EventCollection read-only properties

Read-only properties inherited from ProtoM21Object:

EventCollection methods

EventCollection.addLastSpineEvent(spineNum: int, spineEvent: SpineEvent) → None[source]
EventCollection.addSpineEvent(spineNum: int, spineEvent: SpineEvent) → None[source]
EventCollection.getAllOccurring() → list[SpineEvent | None][source]
EventCollection.getSpineEvent(spineNum: int) → SpineEvent | None[source]
EventCollection.getSpineOccurring(spineNum: int) → SpineEvent | None[source]

GlobalComment

class music21.humdrum.spineParser.GlobalComment(comment: str = '', **keywords)[source]

A Music21Object that represents a comment for the whole score.

>>> sc = humdrum.spineParser.GlobalComment('!! this is a global comment')
>>> sc
<music21.humdrum.spineParser.GlobalComment 'this is a global comment'>
>>> sc.comment
'this is a global comment'

GlobalComment bases

GlobalComment read-only properties

Read-only properties inherited from Music21Object:

Read-only properties inherited from ProtoM21Object:

GlobalComment read/write properties

Read/write properties inherited from Music21Object:

GlobalComment methods

Methods inherited from Music21Object:

GlobalComment instance variables

Instance variables inherited from Music21Object:

GlobalCommentLine

class music21.humdrum.spineParser.GlobalCommentLine(lineNumber: int = 0, contents: str = '')[source]

A GlobalCommentLine is a Humdrum comment that pertains to all spines. In Humdrum it is represented by two exclamation points (and usually one space).

The GlobalComment object takes two inputs and stores them as attributes:

lineNumber (1-based line number in the Humdrum file)
contents   (string of contents)
value      (contents minus !!)

The constructor can be passed (lineNumber, contents); if contents begins with bangs, they are removed along with up to one space directly afterwards.

>>> com1 = humdrum.spineParser.GlobalCommentLine(
...          lineNumber=4, contents='!! this comment is global')
>>> com1
<music21.humdrum.spineParser.GlobalCommentLine 'this comment is global'>
>>> com1.lineNumber
4
>>> com1.contents
'!! this comment is global'
>>> com1.value
'this comment is global'

GlobalCommentLine bases

GlobalCommentLine read-only properties

Read-only properties inherited from ProtoM21Object:

GlobalReference

class music21.humdrum.spineParser.GlobalReference(codeOrAll: str = '', valueOrNone: str | None = None)[source]

An item of metadata or “library-type information” called a “Reference Record” in Humdrum. The music21 term GlobalReference uses Global in the sense that it applies to all spines, but also to the score as a whole.

All GlobalReferences are stored in SpineParser’s .globalReferences array during parsing and then stored in the Score’s metadata at the end of parsing. The position of a GlobalReference in the Humdrum file does not affect where it is placed in the score’s metadata (though later ones will overwrite earlier ones). Humdrum tradition is to place most important reference records at the top of the file and less important ones at the end.

>>> sc = humdrum.spineParser.GlobalReference('!!!REF:this is a global reference')
>>> sc
<music21.humdrum.spineParser.GlobalReference REF 'this is a global reference'>
>>> sc.code
'REF'
>>> sc.value
'this is a global reference'

Alternate form:

>>> sc = humdrum.spineParser.GlobalReference('REF', 'this is a global reference')
>>> sc
<music21.humdrum.spineParser.GlobalReference REF 'this is a global reference'>
>>> sc.code
'REF'
>>> sc.value
'this is a global reference'

Language codes are parsed:

>>> sc = humdrum.spineParser.GlobalReference('!!!OPT@@RUS: Vesna svyashchennaya')
>>> sc.code
'OPT'
>>> sc.language
'RUS'
>>> sc.isPrimary
True
>>> sc = humdrum.spineParser.GlobalReference('!!!OPT@FRE: Le sacre du printemps')
>>> sc.code
'OPT'
>>> sc.language
'FRE'
>>> sc.isPrimary
False
Changed in v11: GlobalReferences are no longer Music21Object subclasses, since they

should not be placed in locations in a Stream.

GlobalReference bases

GlobalReference read-only properties

Read-only properties inherited from ProtoM21Object:

GlobalReference methods

GlobalReference.updateMetadata(md: Metadata) → None[source]

Update a metadata object according to information in this GlobalReference.

See Humdrum guide Appendix I for information.

GlobalReferenceLine

class music21.humdrum.spineParser.GlobalReferenceLine(lineNumber: int = 0, contents: str = '!!! NUL: None')[source]

A GlobalReferenceLine is a type of HumdrumLine that contains a Reference Record or information/metadata about the Humdrum document.

In Humdrum it is represented by three exclamation points followed by non-whitespace followed by a colon. Examples:

!!!COM: Stravinsky, Igor Fyodorovich
!!!CDT: 1882/6/17/-1971/4/6
!!!ODT: 1911//-1913//; 1947//
!!!OPT@@RUS: Vesna svyashchennaya
!!!OPT@FRE: Le sacre du printemps

The GlobalReferenceLine object takes two inputs:

lineNumber   1-based line number in the Humdrum file
contents     string of contents

And stores them as three attributes:

lineNumber: as above
code:       non-whitespace code (usually three letters)
value:      its value
>>> gr = humdrum.spineParser.GlobalReferenceLine(
...        lineNumber=20, contents='!!!COM: Stravinsky, Igor Fyodorovich\n')
>>> gr
<music21.humdrum.spineParser.GlobalReferenceLine COM: 'Stravinsky, Igor Fyodorovich'>
>>> gr.lineNumber
20
>>> gr.code
'COM'
>>> gr.value
'Stravinsky, Igor Fyodorovich'

TODO: add parsing of three-digit Kern comment codes into fuller metadata TODO: parse @/@@ language tags here (e.g. !!!OPT@@RUS:, !!!OPT@FRE:) and propagate language/isPrimary so that GlobalReference.updateMetadata can attach metadata.Text(value, language=...) instead of bare strings.

GlobalReferenceLine bases

GlobalReferenceLine read-only properties

Read-only properties inherited from ProtoM21Object:

HarmSpine

class music21.humdrum.spineParser.HarmSpine(spineId: int = 0, eventList: list[SpineEvent] | None = None, streamClass: type[Stream] = <class 'music21.stream.base.Stream'>, parentSpineCollection: SpineCollection | None = None)[source]

A HarmSpine is a type of Humdrum spine with the **harm attribute set and thus events are processed as if they are harmonic analysis annotations in the “harm” syntax.

The harm roman numeral annotations are parsed using a superset of the original **harm Humdrum representation, written for Python and extending the syntax based on other projects like the RomanText and the MuseScore roman numeral notations.

HarmSpine bases

HarmSpine read-only properties

Read-only properties inherited from ProtoM21Object:

HarmSpine read/write properties

Read/write properties inherited from HumdrumSpine:

HarmSpine methods

HarmSpine.parse() → None[source]

Dummy method that pushes all these objects to HumdrumSpine.stream as ElementWrappers. Should be overridden in specific Spine subclasses.

Methods inherited from HumdrumSpine:

HumdrumDataCollection

class music21.humdrum.spineParser.HumdrumDataCollection(dataStream: str | list[str])[source]

Note

Most users should not need to be here. Just run converter.parse('file.krn') and it will give you a stream.Score instead. Intermediate users who want to get into the guts of Humdrum are still probably better off running humdrum.parseFile(‘filename’) which returns a humdrum.SpineCollection directly.

A HumdrumDataCollection takes in a mandatory list where each element is a line of Humdrum data. Together this list represents a collection of spines. Essentially it’s the contents of a Humdrum file.

Usually you will probably want to use HumdrumFile which can read in a file directly. This class exists in case you have your Humdrum data in another format (database, from the web, etc.) and already have it as a string.

LIMITATIONS: (1) Spines cannot change definition (**exclusive interpretations) mid-spine.

So if you start off with **kern, the rest of the spine needs to be **kern (actually, the first exclusive interpretation for a spine is used throughout).

Note that, even though changing exclusive interpretations mid-spine seems to be legal by the Humdrum definition, it looks like none of the conventional Humdrum parsing tools allow for changing definitions mid-spine, so I don’t think this limitation is a problem. (Craig Stuart Sapp confirmed this to me.)

The Aarden/Miller Palestrina dataset uses \*- followed by **kern at the changes of sections, so some parsing of multiple exclusive interpretations in a protospine may be necessary. But none change definition.

  1. Split spines are assumed to be voices in a single spine staff.

HumdrumDataCollection bases

HumdrumDataCollection read-only properties

HumdrumDataCollection.numLines

The number of lines in self.dataStream, including blank lines.

Read-only properties inherited from ProtoM21Object:

HumdrumDataCollection methods

HumdrumDataCollection.createHumdrumSpines(protoSpines: list[ProtoSpine] | None = None, eventCollections: list[EventCollection] | None = None) → SpineCollection[source]

Takes the data from the object’s protoSpines and eventCollections and returns a SpineCollection object that contains HumdrumSpine() objects.

A HumdrumSpine object differs from a ProtoSpine in that it follows spinePathData – a ProtoSpine records all data in a given tab position, and thus might consist of data from several spines that move around. HumdrumSpines are smart enough not to have this limitation.

When we check for spinePathData we look for the following spine path indicators (from HumdrumDoc):

*+    add a new spine (to the right of the current spine)
*-    terminate a current spine
*^    split a spine (into two)
*v    join (two or more) spines into one
*x    exchange the position of two spines
*     do nothing
HumdrumDataCollection.determineIfDataStreamIsOpus(dataStream: list[str] | None = None) → tuple[bool, list[list[str]] | None][source]

Some Humdrum files contain multiple pieces in one file which are better represented as an Opus file containing multiple scores.

This method examines the dataStream (or self.dataStream) and if it only has a single piece then it returns (False, None).

If it has multiple pieces, it returns True and a list of dataStreams.

>>> from pprint import pprint as pp
>>> mps = humdrum.testFiles.multipartSanctus
>>> hdc = humdrum.spineParser.HumdrumDataCollection(mps)
>>> (hasOpus, dataCollections) = hdc.determineIfDataStreamIsOpus()
>>> hasOpus
True
>>> pp(dataCollections)
[['!!!COM: Palestrina, Giovanni Perluigi da',
  '**kern\t**kern\t**kern\t**kern',
  '*Ibass\t*Itenor\t*Icalto\t*Icant',
  '!Bassus\t!Tenor\t!Altus\t!Cantus',
  '*clefF4\t*clefGv2\t*clefG2\t*clefG2',
  '*M4/2\t*M4/2\t*M4/2\t*M4/2',
  '=1\t=1\t=1\t=1',
  '0r\t0r\t1g\t1r',
  '.\t.\t1a\t1cc',
  '=2\t=2\t=2\t=2',
  '0r\t0r\t1g\t1dd',
  '.\t.\t1r\t1cc',
  '*-\t*-\t*-\t*-'],
 ['!! Pleni',
  '**kern\t**kern\t**kern',
  '*Ibass\t*Itenor\t*Icalto',
  '*clefF4\t*clefGv2\t*clefG2',
  '*M4/2\t*M4/2\t*M4/2',
  '=3\t=3\t=3',
  '1G\t1r\t0r',
  '1A\t1c\t.',
  '=4\t=4\t=4',
  '1B\t1d\t1r',
  '1c\t1e\t1g',
  '*-\t*-\t*-'],
 ['!! Hosanna',
  '**kern\t**kern\t**kern\t**kern',
  '*Ibass\t*Itenor\t*Icalto\t*Icant',
  '*clefF4\t*clefGv2\t*clefG2\t*clefG2',
  '*M3/2\t*M3/2\t*M3/2\t*M3/2',
  '=5\t=5\t=5\t=5',
  '1r\t1r\t1g\t1r',
  '2r\t2r\t[2a\t[2cc',
  '=5\t=5\t=5\t=5',
  '1r\t1r\t2a]\t2cc]',
  '.\t.\t2f\t1dd',
  '2r\t2r\t2g\t.',
  '*-\t*-\t*-\t*-']]
HumdrumDataCollection.insertGlobalEvents() → None[source]

Insert GlobalComments (from GlobalCommentLines) into the outer Stream, and collect GlobalReferences (from GlobalReferenceLines) into self.globalReferences. References become metadata in parseMetadata(), not Stream objects, so they are not inserted here.

Run after self.spineCollection.createMusic21Streams(). Is run automatically by self.parse(). Uses self.spineCollection.getOffsetsAndPrioritiesByLineNumber().

HumdrumDataCollection.parse() → Score | Opus[source]

Parse self.dataStream into music21 objects, returning the resulting Score (or Opus for multipart files like the Palestrina dataset).

The parsed stream is also stored on self.stream.

HumdrumDataCollection.parseEventListFromDataStream(dataStream: list[str] | None = None) → list[HumdrumLine][source]

Sets self.eventList from a dataStream (that is, a list of lines). It sets self.maxSpines to the largest number of spine events found on any line in the file.

The differences between the dataStream and self.eventList are the following:

Returns eventList in addition to setting it as self.eventList.

>>> eventString = ('!!! COM: Beethoven, Ludwig van\n'
...                '!! Not really a piece by Beethoven\n'
...                '**kern\t**dynam\n'
...                'C4\tpp\n'
...                'D8\t.\n')
>>> hdc = humdrum.spineParser.HumdrumDataCollection(eventString)
>>> hdc.maxSpines = 2
>>> eList = hdc.parseEventListFromDataStream()
>>> eList is hdc.eventList
True
>>> for e in eList:
...     print(e)
<music21.humdrum.spineParser.GlobalReferenceLine COM: 'Beethoven, Ludwig van'>
<music21.humdrum.spineParser.GlobalCommentLine 'Not really a piece by Beethoven'>
<music21.humdrum.spineParser.SpineLine line 3 (2 spines)>
<music21.humdrum.spineParser.SpineLine line 4 (2 spines)>
<music21.humdrum.spineParser.SpineLine line 5 (2 spines)>
>>> print(eList[0].value)
Beethoven, Ludwig van

Print line number 4 (which is eList[3] in Python’s zero-indexed format)

>>> print(eList[3].spineData)
['C4', 'pp']
HumdrumDataCollection.parseMetadata(s: Stream | None = None) → None[source]

Create a metadata object for the file from the GlobalReferences collected by insertGlobalEvents().

HumdrumDataCollection.parseNonOpus(dataStream: list[str]) → Score[source]

The main parse function for non-opus data collections.

Populates self.stream (a Score) and returns it.

HumdrumDataCollection.parseOpusDataCollections(dataCollections: list[list[str]]) → Opus[source]

Take a dataCollection from determineIfDataStreamIsOpus and set self.stream to be an Opus instead.

>>> mps = humdrum.testFiles.multipartSanctus
>>> hdc = humdrum.spineParser.HumdrumDataCollection(mps)
>>> (hasOpus, dataCollections) = hdc.determineIfDataStreamIsOpus()
>>> if hasOpus is True:
...     op = hdc.parseOpusDataCollections(dataCollections)
...     print(len(op.scores))
...     for sc in op.scores:
...        print(sc)
3
<music21.stream.Score section_1>
<music21.stream.Score section_2>
<music21.stream.Score section_3>
HumdrumDataCollection.parseProtoSpinesAndEventCollections() → tuple[list[ProtoSpine], list[EventCollection]][source]

Run after parseEventListFromDataStream() to take self.eventList and slice it horizontally to get self.eventCollections, which is a list of EventCollection objects, or things that happen simultaneously.

And, more importantly, this method slices self.eventList vertically to get self.protoSpines which is a list of ProtoSpines, that is a vertical slice of everything that happens in a column, regardless of spine-path indicators.

EventCollection objects store global events. ProtoSpines do not.

So self.eventCollections and self.protoSpines can each be thought of as a two-dimensional sheet of cells, but where the first index of the former is the vertical index in the dataStream and the first index of the latter is the horizontal index in the dataStream. The contents of each cell is a SpineEvent object or None (if there’s no data at that point). Even ‘.’ (continuation events) get translated into SpineEvent objects.

Calls parseEventListFromDataStream() if it hasn’t already been called.

Returns a tuple of protoSpines and eventCollections in addition to setting it in the calling object.

>>> eventString = ('!!!COM: Beethoven, Ludwig van\n'
...                '!! Not really a piece by Beethoven\n'
...                '**kern\t**dynam\n'
...                'C4\tpp\n'
...                'D8\t.\n')
>>> hdc = humdrum.spineParser.HumdrumDataCollection(eventString)
>>> hdc.maxSpines = 2
>>> protoSpines, eventCollections = hdc.parseProtoSpinesAndEventCollections()
>>> protoSpines is hdc.protoSpines
True
>>> eventCollections is hdc.eventCollections
True

Looking at individual slices is unlikely to tell you much:

>>> for thisSlice in eventCollections:
...    print(thisSlice)
<music21.humdrum.spineParser.EventCollection line 1, 2 events, 2 spines>
<music21.humdrum.spineParser.EventCollection line 2, 2 events, 2 spines>
<music21.humdrum.spineParser.EventCollection line 3, 2 events, 2 spines>
<music21.humdrum.spineParser.EventCollection line 4, 2 events, 2 spines>
<music21.humdrum.spineParser.EventCollection line 5, 2 events, 2 spines>
>>> for thisSlice in protoSpines:
...    print(thisSlice)
<music21.humdrum.spineParser.ProtoSpine 5 events>
<music21.humdrum.spineParser.ProtoSpine 5 events>

But looking at the individual slices is revealing:

>>> eventCollections[4].getAllOccurring()
[<music21.humdrum.spineParser.SpineEvent D8>, <music21.humdrum.spineParser.SpineEvent pp>]
>>> for protoEvent in protoSpines[0]:
...    print(repr(protoEvent))
None
None
<music21.humdrum.spineParser.SpineEvent **kern>
<music21.humdrum.spineParser.SpineEvent C4>
<music21.humdrum.spineParser.SpineEvent D8>

HumdrumFile

class music21.humdrum.spineParser.HumdrumFile(filename: str | pathlib.Path)[source]

A HumdrumFile is a HumdrumDataCollection which takes as a mandatory argument a filename to be opened and read.

HumdrumFile bases

HumdrumFile read-only properties

Read-only properties inherited from HumdrumDataCollection:

Read-only properties inherited from ProtoM21Object:

HumdrumFile methods

HumdrumFile.parseFileHandle(fileHandle: Iterable[str]) → Score | Opus[source]

Read all lines from fileHandle into self.dataStream, then call parse() and return its result (a Score or Opus).

HumdrumFile.parseFilename(filename: str | pathlib.Path | None = None) → None[source]

Methods inherited from HumdrumDataCollection:

HumdrumLine

class music21.humdrum.spineParser.HumdrumLine[source]

HumdrumLine is a dummy class for subclassing SpineLine, GlobalCommentLine, and GlobalReferenceLine classes, all of which represent one horizontal line of text in a HumdrumDataCollection that is aware of its position in the file.

See the documentation for the specific classes mentioned above for more details.

HumdrumLine bases

HumdrumLine read-only properties

Read-only properties inherited from ProtoM21Object:

HumdrumSpine

class music21.humdrum.spineParser.HumdrumSpine(spineId: int = 0, eventList: list[SpineEvent] | None = None, streamClass: type[Stream] = <class 'music21.stream.base.Stream'>, parentSpineCollection: SpineCollection | None = None)[source]

A HumdrumSpine is a representation of a generic HumdrumSpine regardless of **definition after spine path indicators have been simplified.

A subclass of HumdrumSpine should be defined for each **type. Those subclasses cannot redefine __init__().

A HumdrumSpine is a collection of events arranged vertically that have a connection to each other. Each HumdrumSpine MUST have an id (numeric or string) attached to it.

>>> SE = humdrum.spineParser.SpineEvent
>>> spineEvents = [SE('**kern'), SE('c,4'), SE('d#8')]
>>> spine1Id = 5
>>> spine1 = humdrum.spineParser.HumdrumSpine(spine1Id, spineEvents)
>>> spine1.insertPoint = 5
>>> spine1.endingPoint = 6
>>> spine1.parentSpine = 3  # spine 3 is the previous spine leading to this one
>>> spine1.childSpines = [7, 8]  # the spine ends by being split into spines 7 and 8

A spine knows the SpineCollection it belongs to:

>>> spineCollection1 = humdrum.spineParser.SpineCollection()
>>> spine1.parentSpineCollection = spineCollection1

The spineType property searches the EventList or parentSpine to figure out the spineType.

>>> spine1.spineType
'kern'

Spines can be iterated through:

>>> for e in spine1:
...    print(e)
**kern
c,4
d#8

If you’d eventually like this spine to be converted to a class other than Stream, pass its classname in as the streamClass argument:

>>> spine2 = humdrum.spineParser.HumdrumSpine(streamClass=stream.Part)
>>> spine2.stream
<music21.stream.Part ...>

HumdrumSpine bases

HumdrumSpine read-only properties

Read-only properties inherited from ProtoM21Object:

HumdrumSpine read/write properties

HumdrumSpine.spineType

Searches the current and parent spineType.

HumdrumSpine methods

HumdrumSpine.append(event: SpineEvent) → None[source]

Add an item to this Spine.

HumdrumSpine.moveElementsIntoMeasures(streamIn: Stream) → Stream[source]

Takes a parsed stream and moves the elements inside the measures. Works with pickup measures, etc. Does not automatically create ties, etc.

Why not just use Stream.makeMeasures()? Because Humdrum measures contain extra information about barlines etc. and pickups are explicitly defined.

>>> s1 = stream.Stream()
>>> s1.append(meter.TimeSignature('2/4'))
>>> m1 = stream.Measure()
>>> m1.number = 1
>>> s1.append(m1)
>>> s1.append(note.Note('C4', type='quarter'))
>>> m2 = stream.Measure()
>>> m2.number = 2
>>> s1.append(m2)
>>> s1.append(note.Note('D4', type='half'))
>>> s1.show('text')
{0.0} <music21.stream.Measure 1 offset=0.0>

{0.0} <music21.meter.TimeSignature 2/4>
{0.0} <music21.note.Note C>
{1.0} <music21.stream.Measure 2 offset=1.0>

{1.0} <music21.note.Note D>
>>> hds = humdrum.spineParser.HumdrumSpine()
>>> s2 = hds.moveElementsIntoMeasures(s1)
>>> s2.show('text')
{0.0} <music21.stream.Measure 1 offset=0.0>
    {0.0} <music21.meter.TimeSignature 2/4>
    {0.0} <music21.note.Note C>
{1.0} <music21.stream.Measure 2 offset=1.0>
    {0.0} <music21.note.Note D>

The first measure is correctly identified as a pickup!

>>> s2.measure(1).paddingLeft
1.0
HumdrumSpine.parse() → None[source]

Dummy method that pushes all these objects to HumdrumSpine.stream as ElementWrappers. Should be overridden in specific Spine subclasses.

KernSpine

class music21.humdrum.spineParser.KernSpine(spineId: int = 0, eventList: list[SpineEvent] | None = None, streamClass: type[Stream] = <class 'music21.stream.base.Stream'>, parentSpineCollection: SpineCollection | None = None)[source]

A KernSpine is a type of Humdrum spine with the **kern attribute set and thus events are processed as if they are kern notes.

Instances are not constructed directly; reclassSpines reassigns __class__ from HumdrumSpine to KernSpine once the spine’s exclusive interpretation is known. All KernSpine-specific state is initialized at the top of parse().

  • Changed in v10: currentTupletDuration / desiredTupletDuration

    renamed to currentTupletQL / desiredTupletQL.

KernSpine bases

KernSpine read-only properties

Read-only properties inherited from ProtoM21Object:

KernSpine read/write properties

Read/write properties inherited from HumdrumSpine:

KernSpine methods

KernSpine.parse() → None[source]

Dummy method that pushes all these objects to HumdrumSpine.stream as ElementWrappers. Should be overridden in specific Spine subclasses.

KernSpine.processChordEvent(eventC: str) → Chord[source]

Process a single chord event.

Like processNoteEvent, stores information about current beam and tuplet state.

The same duration object is normally used for each member of the chord whether they write their duration out (‘8C 8E 8G’) or others omit them (‘8C E G’ - not in spec, but often seen). Humdrum spec does not technically allow different durations (‘8C 8E 4G’), but it is seen (Bach) so we support it.

KernSpine.processNoteEvent(eventC: str) → GeneralNote[source]

Similar to hdStringToNote, this method processes a string representing a single note, but stores information about current beam and tuplet state.

KernSpine.setBeamsForNote(n: GeneralNote) → None[source]

Sets the beams for a Note (or Chord) given self.currentBeamNumbers and updates self.currentBeamNumbers based on stop beams.

Safe enough to use on elements such as rests that don’t have beam info.

KernSpine.setTupletTypeForNote(n: GeneralNote) → None[source]
KernSpine.setup() → None[source]

Pre-parse setup routines for KernSpine.

These set defaults for attributes which would normally be defined in __init__ if we weren’t reclassing spines.

Methods inherited from HumdrumSpine:

MiscTandem

class music21.humdrum.spineParser.MiscTandem(tandem: str = '', **keywords)[source]

MiscTandem bases

MiscTandem read-only properties

Read-only properties inherited from Music21Object:

Read-only properties inherited from ProtoM21Object:

MiscTandem read/write properties

Read/write properties inherited from Music21Object:

MiscTandem methods

Methods inherited from Music21Object:

MiscTandem instance variables

Instance variables inherited from Music21Object:

ProtoSpine

class music21.humdrum.spineParser.ProtoSpine(eventList: list[SpineEvent | None] | None = None)[source]

A ProtoSpine is a collection of events arranged vertically. It differs from a HumdrumSpine in that spine paths are not followed. So ProtoSpine(1) would be everything in the 2nd column of a Humdrum file regardless of whether the 2nd column was at some point an independent Spine or if it later became a split from the first spine.

See parseProtoSpinesAndEventCollections() for more details on how ProtoSpine objects are created.

  • Changed in v10: ProtoSpines now iterate over their eventLists and are ProtoM21Objects.

ProtoSpine bases

ProtoSpine read-only properties

Read-only properties inherited from ProtoM21Object:

SpineCollection

class music21.humdrum.spineParser.SpineCollection[source]

A SpineCollection is a set of HumdrumSpines with relationships to each other and where their position attributes indicate simultaneous onsets.

When you iterate over a SpineCollection, it goes from right to left, since that’s the order that Humdrum expects.

SpineCollection bases

SpineCollection read-only properties

Read-only properties inherited from ProtoM21Object:

SpineCollection methods

SpineCollection.addSpine(streamClass: type[Stream] = <class 'music21.stream.base.Part'>) → HumdrumSpine[source]

Creates a new spine in the collection and returns it.

By default, the underlying music21 class of the spine is Part. This can be overridden by passing in a different streamClass.

Automatically sets the id of the Spine.

>>> hsc = humdrum.spineParser.SpineCollection()
>>> newSpine = hsc.addSpine()
>>> newSpine.id
0
>>> newSpine.stream
<music21.stream.Part ...>
>>> newSpine2 = hsc.addSpine(streamClass=stream.Stream)
>>> newSpine2.id
1
>>> newSpine2
<music21.humdrum.spineParser.HumdrumSpine: 1>
>>> newSpine2.stream
<music21.stream.Stream ...>
SpineCollection.appendSpine(spine: HumdrumSpine) → None[source]

appendSpine(spine) – appends an already existing HumdrumSpine to the SpineCollection. Returns void.

SpineCollection.assignIds() → None[source]

Assign an ID based on the instrument, or just a string of a number if there is no instrument.

>>> hsc = humdrum.spineParser.SpineCollection()
>>> newSpineDefault = hsc.addSpine()
>>> newSpineDefault2 = hsc.addSpine()
>>> newSpineOboe1 = hsc.addSpine()
>>> newSpineOboe2 = hsc.addSpine()
>>> newSpineTrumpet = hsc.addSpine()
>>> newSpineOboe1.stream.append(instrument.Oboe())
>>> newSpineOboe2.stream.append(instrument.Oboe())
>>> newSpineTrumpet.stream.append(instrument.Trumpet())
>>> hsc.assignIds()
>>> for sp in hsc.spines:
...     print(sp.stream.id)
1
2
Oboe-1
Oboe-2
Trumpet
SpineCollection.attachNonKernEvents() → None[source]

Move **dynam and **lyrics/**text information to the appropriate staff.

Assumes that *staff is consistent through the spine.

TODO: misaligned events (a **dynam or **harm whose source line has ‘.’ in the kern column) are silently dropped because the matcher only accepts exact el.priority == event.lineNumber. Desired: when no kern note sits on the same line, attach to the stream at the offset of the most recently-sounding kern note (ideal: between current and next note’s offsets). Lyrics/text continue to attach to notes only. See humdrum.tests.Test.testDynamAttachedMisaligned / testHarmAttachedMisaligned.

SpineCollection.createMusic21Streams() → None[source]

Create Music21 Stream objects from spineCollections by running:

self.reclassSpines() self.parseMusic21() self.performInsertions() self.moveObjectsToMeasures() self.attachNonKernEvents() self.makeVoices() self.assignIds()

SpineCollection.getOffsetsAndPrioritiesByLineNumber() → dict[int, tuple[OffsetQL, list[Music21Object]]][source]

Iterates through the spines by location and records the offset and priority for each element in the spines.

Returns a dictionary keyed by Humdrum line number whose values are two-element tuples: (music21 offset, list of Music21Objects at that line).

SpineCollection.getSpineById(spineId: int) → HumdrumSpine[source]

Returns the HumdrumSpine with the given id.

Raises a HumdrumException if the spine with a given id is not found.

SpineCollection.makeVoices() → None[source]

Make voices for each kernSpine – why not just run stream.makeVoices() ? Because we have more information here that lets us make voices more intelligently.

Should be done after measures have been made.

SpineCollection.moveObjectsToMeasures() → None[source]

Run moveElementsIntoMeasures for each HumdrumSpine that is not a sub-spine.

Also fixes up the tuplets using duration.TupletFixer.

SpineCollection.parseMusic21() → None[source]

Runs spine.parse() for each Spine, thus populating the spine.stream for each Spine.

SpineCollection.performInsertions() → None[source]

Take a parsed spineCollection as Music21Objects and take sub-spines and put them in their proper location.

SpineCollection.performSpineInsertion(thisSpine: HumdrumSpine, newStream: Stream, insertionPoint: int) → None[source]

Insert all the spines into newStream that should be inserted into thisSpine at insertionPoint.

SpineCollection.reclassSpines() → None[source]

Changes the classes of HumdrumSpines to more specific types (KernSpine, DynamSpine) according to their spineType (e.g., **kern, **dynam).

SpineCollection.removeSpineById(spineId: int) → None[source]

Deletes a spine from the SpineCollection (after inserting, integrating, etc.)

>>> hsc = humdrum.spineParser.SpineCollection()
>>> newSpine = hsc.addSpine()
>>> newSpine.id
0
>>> newSpine2 = hsc.addSpine()
>>> newSpine2.id
1
>>> hsc.spines
[<music21.humdrum.spineParser.HumdrumSpine: 0>,
 <music21.humdrum.spineParser.HumdrumSpine: 1>]
>>> hsc.removeSpineById(newSpine.id)
>>> hsc.spines
[<music21.humdrum.spineParser.HumdrumSpine: 1>]

Raises a HumdrumException if the spine with a given id is not found.

SpineComment

class music21.humdrum.spineParser.SpineComment(comment: str = '', **keywords)[source]

A Music21Object that represents a comment in a single spine.

>>> sc = humdrum.spineParser.SpineComment('! this is a spine comment')
>>> sc
<music21.humdrum.spineParser.SpineComment 'this is a spine comment'>
>>> sc.comment
'this is a spine comment'

SpineComment bases

SpineComment read-only properties

Read-only properties inherited from Music21Object:

Read-only properties inherited from ProtoM21Object:

SpineComment read/write properties

Read/write properties inherited from Music21Object:

SpineComment methods

Methods inherited from Music21Object:

SpineComment instance variables

Instance variables inherited from Music21Object:

SpineEvent

class music21.humdrum.spineParser.SpineEvent(contents: str = '', lineNumber: int = 0)[source]

A SpineEvent is an event in a HumdrumSpine or ProtoSpine.

Its .contents property contains the contents of the spine, or it could be ‘.’, in which case it means that a particular event appears after the last event in a different spine. It could also be “None” indicating that there is no event at all at this moment in the Humdrum file. Happens if no ProtoSpine exists at this point in the file in this tab position.

Should be initialized with its contents and 1-based source line number.

These attributes are optional but likely to be very helpful:

lineNumber -- 1-based source line number in the Humdrum file
protoSpineId -- ProtoSpine id (0 to N)
spineId -- id of HumdrumSpine actually attached to (after SpinePaths are parsed)

The toNote() method converts the contents into a music21 note as if it’s kern – useful to have in all spine types.

>>> se1 = humdrum.spineParser.SpineEvent('EEE-8')
>>> se1.lineNumber = 40
>>> se1.contents
'EEE-8'
>>> se1
<music21.humdrum.spineParser.SpineEvent EEE-8>
>>> n = se1.toNote()
>>> n
<music21.note.Note E->

SpineEvent bases

SpineEvent read-only properties

Read-only properties inherited from ProtoM21Object:

SpineEvent methods

SpineEvent.toNote(convertString: str | None = None) → GeneralNote[source]

Parse the object as a **kern note and return a Note object (or Rest, or Chord).

>>> se = humdrum.spineParser.SpineEvent('DD#4')
>>> n = se.toNote()
>>> n
<music21.note.Note D#>
>>> n.octave
2

Default duration is a quarter note.

>>> n.duration.type
'quarter'

SpineLine

class music21.humdrum.spineParser.SpineLine(lineNumber: int = 0, contents: str = '')[source]

A SpineLine is any horizontal line of a Humdrum file that contains one or more spine elements (separated by tabs) and not Global elements.

Takes in a 1-based line number in the file and a string of contents.

>>> hsl = humdrum.spineParser.SpineLine(
...         lineNumber=7, contents='C4\t!local comment\t*M[4/4]\t.\n')
>>> hsl
<music21.humdrum.spineParser.SpineLine line 7 (4 spines)>
>>> hsl.lineNumber
7
>>> hsl.numSpines
4
>>> hsl.spineData
['C4', '!local comment', '*M[4/4]', '.']

SpineLine bases

SpineLine read-only properties

Read-only properties inherited from ProtoM21Object:

Functions

music21.humdrum.spineParser.extractDuration(eventC: str) → tuple[str, str][source]

Extract duration characters from a token, returning what is left.

>>> humdrum.spineParser.extractDuration('2cc.')
('2.', 'cc')
>>> humdrum.spineParser.extractDuration('3%2D')
('3%2', 'D')
>>> humdrum.spineParser.extractDuration('B#')
('', 'B#')

For **kern, this routine should be run separately for each component of a chord.

New in v11.

music21.humdrum.spineParser.hdStringToDuration(contents: str) → Duration[source]

Convert contents to a Duration object.

>>> d = humdrum.spineParser.hdStringToDuration('32')
>>> d
<music21.duration.Duration 0.125>
>>> d.type
'32nd'

Other information such as pitch can be passed here (and in practice is passed here) without affecting parsing, such as the C# here.

>>> d = humdrum.spineParser.hdStringToDuration('8C#..')
>>> d.type
'eighth'
>>> d.dots
2

Tuplets are represented with the number of them in a whole note, such as 12 for eighth-note triplets:

>>> d = humdrum.spineParser.hdStringToDuration('12')
>>> d.fullName
'Eighth Triplet (1/3 QL)'
>>> d.type
'eighth'
>>> d.tuplets
(<music21.duration.Tuplet 3/2/eighth>,)

Grace notes are represented by q. A grace note without duration is an eighth note:

>>> d = humdrum.spineParser.hdStringToDuration('gg#q/LL')
>>> d
<music21.duration.GraceDuration unlinked type:eighth quarterLength:0.0>
>>> d.isGrace
True

If a duration, like 16, is given, that type is preserved in the unlinked GraceDuration.

>>> d = humdrum.spineParser.hdStringToDuration('16ccq')
>>> d
<music21.duration.GraceDuration unlinked type:16th quarterLength:0.0>

A single q is a slashed grace note (acciaccatura):

>>> d.slash
True

Two qs signify a grace note without a slash (or accented grace note):

>>> d = humdrum.spineParser.hdStringToDuration('16ccqq')
>>> d.type
'16th'
>>> d.slash
False

Note

A capital Q was the original Humdrum way of signifying these notes (gruppettos). Some post-2020 encodings use Q to mean a cue-sized note. Given no way of distinguishing these interpretations, music21 will remain with the original version; so Q and qq are synonyms.

New rhythmic extensions formerly defined in wiki.humdrum.org/index.php/Rational_rhythms and now at https://extras.humdrum.org/man/rscale/ are fully implemented:

>>> d = humdrum.spineParser.hdStringToDuration('CC3%2')
>>> d.quarterLength
Fraction(8, 3)
>>> d.fullName
'Whole Triplet (2 2/3 QL)'
>>> d = humdrum.spineParser.hdStringToDuration('e-00.')
>>> d.quarterLength
24.0
>>> d.fullName
'Perfect Longa'
>>> d = humdrum.spineParser.hdStringToDuration('F#000')
>>> d.quarterLength
32.0
>>> d.fullName
'Imperfect Maxima'

Note that the following example is interpreted as one note in the time of a double-dotted quarter (not a double-dotted quarter-note triplet).

>>> d = humdrum.spineParser.hdStringToDuration('6..fff')
>>> d.quarterLength
Fraction(7, 6)
>>> d.dots
0
>>> d.tuplets[0].durationNormal.dots
2
>>> d.tuplets[0].totalTupletLength()
3.5

The latter definition (a double-dotted quarter-note triplet), though used in https://kern.humdrum.org/cgi-bin/ksdata?l=musedata/mozart/quartet&file=k421-01.krn&f=kern and the Josquin Research Project (JRP), is incorrect, seeing as it contradicts the specification in https://www.humdrum.org/Humdrum/representations/kern.html#N-Tuplets (_”92..” is the correct **kern encoding for a note whose duration is 23 notes in the time of a doubly-dotted quarter._)

We will call the second interpretation the “JRP Flavor”. The default JRP Flavor is False:

>>> humdrum.spineParser.flavors['JRP']
False

If you want the JRP definition, where dots apply to the note, not tuplet, set humdrum.spineParser.flavors[‘JRP’] = True before calling converter.parse() or any other parsing routine like hdStringToDuration:

>>> humdrum.spineParser.flavors['JRP'] = True
>>> d = humdrum.spineParser.hdStringToDuration('6..fff')

Note that the quarter length of the individual note is the same:

>>> d.quarterLength
Fraction(7, 6)

But the representation of how many dots are on the note as opposed to the total tuplet duration is quite different.

>>> d.dots
2
>>> d.tuplets[0].durationNormal.dots
0
>>> d.tuplets[0].totalTupletLength()
2.0

If no duration number can be found and this is not a grace note (which gets a default eighth-note duration), then a HumdrumException is raised.

>>> humdrum.spineParser.hdStringToDuration('nada')
Traceback (most recent call last):
music21.humdrum.spineParser.HumdrumException: Could not find a duration in 'nada'
  • Changed in v11: Duration extraction moved to hdStringToDuration.

    Grace notes keep their written duration, and qq is parsed as an unslashed grace note.

music21.humdrum.spineParser.hdStringToMeasure(contents: str, previousMeasure: Measure | None = None) → Measure[source]

Kern uses an equals sign followed by processing instructions to create new measures.

  • Changed in v10: repeat dots are encoded as Repeat

    objects instead of being stuffed into a non-existent Barline.repeatDots attribute. :|: now produces an end-Repeat on the previous measure’s right barline AND a start-Repeat on the new measure’s left barline.

music21.humdrum.spineParser.hdStringToNote(contents: str, *, overrideDuration: Duration | None = None) → GeneralNote[source]

Returns a Note (or Rest or Unpitched, etc.) matching the current SpineEvent.

Does not check to see that it is sane or part of a **kern spine, etc.

If contents has no written duration, the note should have had overrideDuration given, as happens with chord members.

>>> n = humdrum.spineParser.hdStringToNote('4CC.')
>>> n.fullName
'C in octave 2 Dotted Quarter Note'
>>> n = humdrum.spineParser.hdStringToNote('12dd##')
>>> n.fullName
'D-double-sharp in octave 5 Eighth Triplet (1/3 QL) Note'

If overrideDuration is passed in as a Duration object, that is used no matter what:

>>> n = humdrum.spineParser.hdStringToNote('12dd##',
...         overrideDuration=duration.Duration('whole'))
>>> n.duration
<music21.duration.Duration 4.0>
>>> n.duration.type
'whole'

The overrideDuration is needed if a note is not a grace note. For instance, for chord members after the first one.

>>> n = humdrum.spineParser.hdStringToNote('BBB--',
...         overrideDuration=duration.Duration('eighth'))
>>> n.fullName
'B-double-flat in octave 1 Eighth Note'

Otherwise, it uses a quarter note and gives a warning. This may become an error in future versions:

>>> import warnings
>>> with warnings.catch_warnings(record=True) as w:
...      n = humdrum.spineParser.hdStringToNote('B-')
...      n.fullName
'B-flat in octave 3 Quarter Note'
>>> w[0].message
UserWarning("Could not find a duration in 'B-', using quarter note.")
  • Changed in v11: give a warning if no duration can be found and a quarter default was used.

music21.humdrum.spineParser.kernTandemToObject(tandem: str) → Music21Object | None[source]

Kern uses symbols such as *M5/4 and *clefG2, etc., to control processing.

This method converts them to music21 objects.

>>> m = humdrum.spineParser.kernTandemToObject('*M3/1')
>>> m
<music21.meter.TimeSignature 3/1>

Unknown objects are converted to MiscTandem objects:

>>> m2 = humdrum.spineParser.kernTandemToObject('*TandyUnk')
>>> m2
<music21.humdrum.spineParser.MiscTandem *TandyUnk>