Skip to content

Documents and Spines: Understanding kernpy's Data Structures

The Document Object

When you load a **kern file using kernpy, you get a Document object. This object contains the entire parsed score in a tree-like structure.

import kernpy as kp

# Load a file
document, errors = kp.load('score.krn')

# The document contains everything
print(type(document))  # <class 'kernpy.core.document.Document'>
print(document)        # kernpy.core.document.Document object

Document Structure

The Document object represents a complete musical score. It provides access to:

  • The score's tree structure (spines, tokens, barlines)
  • Measure information
  • Spine metadata (instrument names, clefs, key signatures)
  • Error information from parsing

What Are Spines?

A spine is a vertical column in a kern file representing a single voice or instrument. When you load a kern file with multiple columns, each column becomes a separate spine.

Example **kern file with three spines:

**kern    **text    **dynam
*Ivioln   *         *
*clefG2   *         *
=1        =1        =1
4c        Do        p
4d        Re        <
4e        Mi        (
4f        Fa        (
=2        =2        =2
2g        Sol       f
2cc       -         >
==        ==        ==
*-        *-        *-

This file has:

  • Spine 0: Musical notation (**kern)
  • Spine 1: Lyrics (**text)
  • Spine 2: Dynamics (**dynam)

Accessing Spines

You can access individual spines from the document:

import kernpy as kp

doc, _ = kp.load('score.krn')

# Get all spines
spines = doc.get_spines()
print(f"Number of spines: {len(spines)}")

# Get a specific spine by index
spine_0 = spines[0]
print(f"Spine 0 type: {spine_0.header}")  # **kern

# Get the header of a spine
spine_type = spine_0.header
print(spine_type)  # **kern, **text, **dynam, etc.

Tokens Within Spines

Each spine contains tokens, which are the individual musical or textual elements:

import kernpy as kp

doc, _ = kp.load('score.krn')
spines = doc.get_spines()

# Get tokens from a spine
kern_spine = spines[0]
tokens = kern_spine.get_tokens()  # or use a similar accessor

for token in tokens:
    print(f"Token: {token.encoding}")  # The raw **kern representation

Measures

Measures are logical divisions of the score marked by barlines. You can work with measures rather than individual tokens:

import kernpy as kp

doc, _ = kp.load('score.krn')

# Get measure information
first_measure = doc.get_first_measure()
last_measure = doc.measures_count()
print(f"Score spans measures {first_measure} to {last_measure}")

# Export specific measures
kp.dump(doc, 'measure_1.krn', from_measure=1, to_measure=1)
kp.dump(doc, 'measures_5_10.krn', from_measure=5, to_measure=10)

The Document Tree

Internally, the document uses a tree structure. You can visualize and navigate it:

import kernpy as kp

doc, _ = kp.load('score.krn')

# Export the tree structure for visualization
tree = doc.tree
print(tree)

# Create a graphviz visualization
kp.graph(doc, 'score_tree.dot')
# You can then render this with: dot -Tpng score_tree.dot -o score_tree.png

The tree helps you understand:

  • The hierarchical structure of the score
  • How tokens are organized
  • Reference points for navigation and analysis

Spine Types and Metadata

Different spine types carry different kinds of information:

import kernpy as kp

doc, _ = kp.load('score.krn')
spine_types = kp.spine_types(doc)
print(spine_types)  # List of spine types: ['**kern', '**text', '**dynam', ...]

# Filter to only kern spines
kern_spines = [s for s in doc.get_spines() if s.header == '**kern']

See concepts/humdrum-kern.md for the list of common spine types.

Selecting Spines for Export

When exporting, you can choose which spines to include:

import kernpy as kp

doc, _ = kp.load('score.krn')

# Export only **kern spines
kp.dump(doc, 'kern_only.krn',
        spine_types=['**kern'])

# Export **kern and **text together
kp.dump(doc, 'kern_and_lyrics.krn',
        spine_types=['**kern', '**text'])

# Export by spine index (0-based)
kp.dump(doc, 'first_two_spines.krn',
        spine_ids=[0, 1])

Working with Document Content

The document gives you many ways to interact with the content:

import kernpy as kp

doc, _ = kp.load('score.krn')

# Get information about the score
num_measures = doc.measures_count()
first_measure_num = doc.get_first_measure()

# Check what spines exist
spines = doc.get_spines()
kern_count = sum(1 for s in spines if s.header == '**kern')
text_count = sum(1 for s in spines if s.header == '**text')

print(f"Score has:")
print(f"  {num_measures} measures (from measure {first_measure_num})")
print(f"  {len(spines)} spines")
print(f"  {kern_count} kern spine(s)")
print(f"  {text_count} text spine(s)")

Using the Tree for Advanced Navigation

For more advanced analysis, you can work directly with the document tree:

import kernpy as kp

doc, _ = kp.load('score.krn')

# Access the tree
tree = doc.tree
print(tree)  # DocumentTree object

# Use the tree to navigate nodes
# (Specific tree navigation methods depend on kernpy's API)

Summary

The Document object is your gateway to everything in a **kern file:

  • Spines — Vertical columns of musical or textual data
  • Tokens — Individual notes, rests, markings, and metadata
  • Measures — Logical divisions of the score
  • Metadata — Information about the score structure and content

By understanding documents and spines, you can effectively load, analyze, filter, and transform musical scores using kernpy.

Next Steps