Skip to contents

Welcome to “Shaping humdrum data”! This article explains various steps you can, and often need, to take to prepare humdrum datasets for analysis, using humdrum\(_{\mathbb{R}}\).

One of the great strengths of the humdrum syntax is its flexibility—there are lots of ways you can structure your data to conveniently represent musical information. However, when it comes time analyze data, we typically want to “reshape” our data into a particular format that is ideal for analysis. The key idea is this: given our data and our particular research question, we must determine/decide what constitutes a single data observation. We want each data observation, or data point, to correspond to one row in our humdrum table. Depending on how your humdrum data files are organized, this might not be the case when you first load your data.

In order to shape our data, there are three steps we might need to do to our data:

  1. Removing information we don’t want.
  2. Splitting apart information that is bundled together.
  3. Pasting together or aligning information that is currently separated.

Consider the following small humdrum file, which is bundled with humdrum\(_{\mathbb{R}}\).

example <- readHumdrum(humdrumRroot, 'examples/Reshaping_example.hum')
example
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:        **kern      **silbe       **kern      **silbe      **harm
>       2:        *ICvox            *       *ICvox            *           *
>       3:        *Ialto            *      *Isoprn            *           *
>       4:         *M4/4            *        *M4/4            *           *
>       5:           *C:            *          *C:            *         *C:
>       6:           4.c         This          4.e         This           I
>       7:            8d           is           8f           is           .
>       8:            4e           an           4g           an           .
>       9:            4f          ex-           4a          ex-          IV
>      10:             =            =            =            =           =
>      11:            2g         -am-          4dd         -am-         I64
>      12:             .            .          4cc            _           .
>      13:            2g         -ple           2b         -ple           V
>      14:            *-           *-           *-           *-          *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>               *Token :: character

This file contains (at least) seven different pieces of information! There are two voices, alto and soprano; Each voice has its rhythm and pitch information encoded in its **kern spine, plus lyrical information in the next **silbe spine. In addition, the **harm spine indicates the harmony accompanying both the vocal parts. Now, depending on what sorts of analyses we want to perform, we need to decide what combinations of these seven information streams consitute a “data observation.”

Filtering Data

The first step is often to simply remove data we don’t need. In this article, we’ll show you the most common, basic, ways you might filter your data. For more details about other humdrum\(_{\mathbb{R}}\) filtering functionality, check out the data filtering article.

Indexing

For example, if we are studying tonality, we might simply want to ignore the lyric data. These easiest way to do this is to index out spines we don’t want, either using numeric indices

example[[ , c(1,3,5)]]
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:                **kern              **kern              **harm
>       2:                *ICvox              *ICvox                   *
>       3:                *Ialto             *Isoprn                   *
>       4:                 *M4/4               *M4/4                   *
>       5:                   *C:                 *C:                 *C:
>       6:                   4.c                 4.e                   I
>       7:                    8d                  8f                   .
>       8:                    4e                  4g                   .
>       9:                    4f                  4a                  IV
>      10:                     =                   =                   =
>      11:                    2g                 4dd                 I64
>      12:                     .                 4cc                   .
>      13:                    2g                  2b                   V
>      14:                    *-                  *-                  *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>               *Token :: character

or (probably better) by exclusive interpretation:

example[[ , c('**kern', '**harm')]]
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:                **kern              **kern              **harm
>       2:                *ICvox              *ICvox                   *
>       3:                *Ialto             *Isoprn                   *
>       4:                 *M4/4               *M4/4                   *
>       5:                   *C:                 *C:                 *C:
>       6:                   4.c                 4.e                   I
>       7:                    8d                  8f                   .
>       8:                    4e                  4g                   .
>       9:                    4f                  4a                  IV
>      10:                     =                   =                   =
>      11:                    2g                 4dd                 I64
>      12:                     .                 4cc                   .
>      13:                    2g                  2b                   V
>      14:                    *-                  *-                  *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>               *Token :: character

Parsing Token

In this file, the **kern spines in our example file include rhythmic data (**recip) and pitch data. If we are “just” studying tonality, we could extract the pitch information from the Token field, and save it to a new field. For example, we can use the kern() function to extract the pitch information from Token, and put it in a new field, which we’ll call Pitch.


example |>
  mutate(Pitch = kern(Token)) -> example

example
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:        **kern      **kern      **kern      **kern      **kern
>       2:             .           .           .           .           .
>       3:             .           .           .           .           .
>       4:             .           .           .           .           .
>       5:           *C:           .         *C:           .         *C:
>       6:             c           .           e           .           .
>       7:             d           .           f           .           .
>       8:             e           .           g           .           .
>       9:             f           .           a           .           .
>      10:             =           =           =           =           =
>      11:             g           .          dd           .           .
>      12:             .           .          cc           .           .
>      13:             g           .           b           .           .
>      14:            *-          *-          *-          *-          *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>               *Pitch :: character (**kern tokens)
>                Token :: character

Having now isolated the pitch information, we can analyze it. (Of course, the rhythm information is still present, in the original Token field.)

Other filtering

Of course, there are many other filtering options you might want to do, depending on your research goals. Perhaps you only want to study pieces/passages in a flat keys, or only works from a particular time period, etc. A common example would be limiting our rhythmic analyses to music in 4/4 time, and perhaps ignoring pickup notes. The TimeSignature field indicates the time signature, and the Bar field can be used as an indicator of pickups at the beginning of pieces, as they are marked Bar == 0. So for example (using the Bach chorale data):

chorales <- readHumdrum(humdrumRroot, 'HumdrumData/BachChorales/.*krn')

chorales |>
  filter(Bar > 0 & TimeSignature == 'M4/4') -> chorales

chorales
>    ######################## vvv chor002.krn vvv #########################
>                1:  !!!COM: Bach, Johann Sebastian
>                2:  !!!CDT: 1685/02/21/-1750/07/28/
>                3:  !!!OTL@@DE: Ich dank dir, lieber Herre
>                4:  !!!SCT: BWV 347
>                5:  !!!PC#: 2
>                6:  !!!AGN: chorale
>                7:           **kern         **kern         **kern         **kern
>                8:           *ICvox         *ICvox         *ICvox         *ICvox
>                9:           *Ibass        *Itenor         *Ialto        *Isoprn
>               10:          *I"Bass       *I"Tenor        *I"Alto     *I"Soprano
>               11:        *>[A,A,B]      *>[A,A,B]      *>[A,A,B]      *>[A,A,B]
>               12:     *>norep[A,B]   *>norep[A,B]   *>norep[A,B]   *>norep[A,B]
>               13:              *>A            *>A            *>A            *>A
>               14:          *clefF4       *clefGv2        *clefG2        *clefG2
>               15:       *k[f#c#g#]     *k[f#c#g#]     *k[f#c#g#]     *k[f#c#g#]
>               16:              *A:            *A:            *A:            *A:
>               17:            *M4/4          *M4/4          *M4/4          *M4/4
>               18:          *met(c)        *met(c)        *met(c)        *met(c)
>               19:           *MM100         *MM100         *MM100         *MM100
>               20:                .              .              .              .
>               21:                .              .              .              .
>               22:               =1             =1             =1             =1
>               23:              4F#            4c#            4f#             4a
>               24:              4C#           8c#L             4e             4a
>               25:                .            8BJ              .              .
>               26:               4D            8AL            4f#             4a
>               27:                .           8G#J              .              .
>               28:              4D#            4F#            4f#             4b
>               29:               =2             =2             =2             =2
>               30:               4E            4.B             4e             4g
>    31-124::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
>    ######################## ^^^ chor002.krn ^^^ #########################
>    
>           (six more pieces...)
>    
>    ######################## vvv chor010.krn vvv #########################
>      1-70::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
>               71:               4D            8F#             4d             4b
>               72:                .             4G              .              .
>               73:               4D              .             4c             4a
>               74:                .            8F#              .              .
>               75:             2GG;            2G;            2B;            2g;
>               76:              =11            =11            =11            =11
>               77:               2C             2G             2e             2g
>               78:              4AA             4A             4e            4cc
>               79:               4E            4G#            8eL             4b
>               80:                .              .            8dJ              .
>               81:              =12            =12            =12            =12
>               82:               4F             4A             4c             4a
>               83:               4C             4G             4c             4e
>               84:             4BB-             4G            [2d             4g
>               85:              4AA             4A              .             4f
>               86:              =13            =13            =13            =13
>               87:             4GG#             4B            4d]            1e;
>               88:              4AA             4A             4c              .
>               89:             2EE;          2G#X;            2B;              .
>               90:               ==             ==             ==             ==
>               91:               *-             *-             *-             *-
>               92:  !!!hum2abc: -Q ''
>               93:  !!!title: @{PC#}. @{OTL@@DE}
>               94:  !!!YOR1: 371 vierstimmige Choralges&auml;nge von Johann Sebastian B***
>               95:  !!!YOR2: 4th ed. by Alfred D&ouml;rffel (Leipzig: Breitkopf und H&a***
>               96:  !!!YOR2: c.1875). 178 pp. Plate "V.A.10".  reprint: J.S. Bach, 371 ***
>               97:  !!!YOR4: Chorales (New York: Associated Music Publishers, Inc., c.1***
>               98:  !!!SMS: B&H, 4th ed, Alfred D&ouml;rffel, c.1875, plate V.A.10
>               99:  !!!EED:  Craig Stuart Sapp
>              100:  !!!EEV:  2009/05/22
>    ######################## ^^^ chor010.krn ^^^ #########################
>                  (***four global comments truncated due to screen size***)
>    
>       humdrumR corpus of eight pieces.
>    
>       Data fields: 
>               *Token :: character

Splitting/Separating Data

Humdrum data often packs multiple pieces of information into compact, concise, readable tokens. The classic example, or course, is **kern itself which often includes rhythm, pitch, phrasing, beaming, and pitch ornamentation information! These tokens are great for reading/writing, but not for analyzing, so we typically want to separate the information we do want.

Isolating Pitch and Rhythm

As we’ve seen, the **kern spines in our example file include rhythmic data (**recip) and pitch data. In some cases, we might want to access both pieces of information, but separately. We can separate them by applying different functions to the Token field, and saving the output to new fields. For example,


example |>
  mutate(Rhythm = recip(Token),
         Pitch = kern(Token)) -> example
example
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:    **recip**kern  **recip**kern  **recip**kern  **recip**kern    ***
>       2:                .              .              .              .    ***
>       3:                .              .              .              .    ***
>       4:            *M4/4              .          *M4/4              .    ***
>       5:              *C:              .            *C:              .    ***
>       6:              4.c              .            4.e              .    ***
>       7:               8d              .             8f              .    ***
>       8:               4e              .             4g              .    ***
>       9:               4f              .             4a              .    ***
>      10:                =              =              =              =    ***
>      11:               2g              .            4dd              .    ***
>      12:                .              .            4cc              .    ***
>      13:               2g              .             2b              .    ***
>      14:               *-             *-             *-             *-    ***
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>                    (***one spine/path not displayed due to screen size***)
>    
>       Data fields: 
>               *Pitch  :: character (**kern tokens)
>               *Rhythm :: character (**recip tokens)
>                Token  :: character

Hey, the printout kind of looks the same…but if you look at the bottom you’ll see that there are now separte Pitch and Rhythm fields. Since both fields are automatically selected by mutate(), we are seeing them both. But we could, for example, select one or the other field,

example |> select(Pitch)
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:        **kern      **kern      **kern      **kern      **kern
>       2:             .           .           .           .           .
>       3:             .           .           .           .           .
>       4:             .           .           .           .           .
>       5:           *C:           .         *C:           .         *C:
>       6:             c           .           e           .           .
>       7:             d           .           f           .           .
>       8:             e           .           g           .           .
>       9:             f           .           a           .           .
>      10:             =           =           =           =           =
>      11:             g           .          dd           .           .
>      12:             .           .          cc           .           .
>      13:             g           .           b           .           .
>      14:            *-          *-          *-          *-          *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>               *Pitch  :: character (**kern tokens)
>                Rhythm :: character (**recip tokens)
>                Token  :: character

example |> select(Rhythm)
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:       **recip     **recip     **recip     **recip     **recip
>       2:             .           .           .           .           .
>       3:             .           .           .           .           .
>       4:         *M4/4           .       *M4/4           .           .
>       5:             .           .           .           .           .
>       6:            4.           .          4.           .           .
>       7:             8           .           8           .           .
>       8:             4           .           4           .           .
>       9:             4           .           4           .           .
>      10:             =           =           =           =           =
>      11:             2           .           4           .          64
>      12:             .           .           4           .           .
>      13:             2           .           2           .           .
>      14:            *-          *-          *-          *-          *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>                Pitch  :: character (**kern tokens)
>               *Rhythm :: character (**recip tokens)
>                Token  :: character

or run commands using them. For example, we could tabulate pitches wherever the rhythmic duration is a quarter note:

example |>
  filter(Rhythm == '4') |>
  count(Pitch)
>    humdrumR count distribution 
>    Pitch  n
>        C  .
>       C#  .
>        D  .
>       E-  .
>        E  .
>        F  .
>       F#  .
>        G  .
>       A-  .
>        A  .
>       B-  .
>        B  .
>        c  .
>       c#  .
>        d  .
>       e-  .
>        e  1
>        f  1
>       f#  .
>        g  1
>       a-  .
>        a  1
>       b-  .
>        b  .
>       cc  1
>      cc#  .
>       dd  1
>      ee-  .
>       ee  .
>       ff  .
>      ff#  .
>       gg  .
>      aa-  .
>       aa  .
>      bb-  .
>       bb  .
>    Pitch  n
>    humdrumR count distribution

This is only possible because we separated the information which was originally “pasted” together in the **kern tokens.

Rend

What if actually want to move the rhythm and pitch information to separate spines—i.e., into their rows in the humdrum table? Use rend():

example |>
  rend(Rhythm, Pitch)
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:       **recip     **recip     **recip     **recip     **recip
>       2:             .           .           .           .           .
>       3:             .           .           .           .           .
>       4:         *M4/4       *M4/4       *M4/4       *M4/4           .
>       5:             .           .           .           .           .
>       6:            4.           c          4.           e           .
>       7:             8           d           8           f           .
>       8:             4           e           4           g           .
>       9:             4           f           4           a           .
>      10:             =           =           =           =           =
>      11:             2           g           4          dd          64
>      12:             .           .           4          cc           .
>      13:             2           g           2           b           .
>      14:            *-          *-          *-          *-          *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>               *Rhythm.Pitch :: character (**recip tokens)
>                Token        :: character

Cleave (Pasting/Aligning)

The next step might be to align/combine information that is currently separated. In many humdrum\(_{\mathbb{R}}\) datasets, we have multiple pieces of information spread across multiple spines, or in some cases, across spine paths or stops. If, given our research question, we need to think of multiple pieces of information as describing a single data point, we’ll need to reshape the data. For example, in our example file the **silbe (lyric) spines associate each syllable with exactly one note in the adjacent **kern spines. Currently, by default, each **kern token and each **silbe token are in their own, separate row of the humdrum table. We want them in the same row. In the humdrum syntax view, this means moving the **silbe data into the same location as the **kern tokens.

Cleaving Spines

To take separate spines—like **kern and **silbe—and paste them together, we use cleave(); as in “to cleave together.” Simply run cleave, and tell it which spines to cleave together. In our example file, the 1st and 3rd spines are **kern while the 2nd and 4th spines are **silbe, so we can tell cleave to cleave together 1:2 and 3:4:

In our example, we want to align the notes in the **kern spines with the syllables in the **silbe spine. We can do this directly using cleave(): use the fold argument to indicate which spine to fold, and the onto argument to indicate which spine to move it onto.

example <- readHumdrum(humdrumRroot, 'examples/Reshaping_example.hum')

example |>
  cleave(1:2, 3:4)
>    ################## vvv Reshaping_example.hum vvv ##################
>       1:         **kern**silbe       **kern**silbe          **harm
>       2:                *ICvox              *ICvox               *
>       3:                *Ialto             *Isoprn               *
>       4:                 *M4/4               *M4/4               *
>       5:                   *C:                 *C:             *C:
>       6:               4.cThis             4.eThis               I
>       7:                  8dis                8fis               .
>       8:                  4ean                4gan               .
>       9:                 4fex-               4aex-              IV
>      10:                     =                   =               =
>      11:                2g-am-             4dd-am-             I64
>      12:                     .                4cc_               .
>      13:                2g-ple              2b-ple               V
>      14:                    *-                  *-              *-
>    ################## ^^^ Reshaping_example.hum ^^^ ##################
>    
>       Data fields: 
>               *Spine2|4 :: character
>               *Token    :: character

It worked! The 1st and 3rd spines have dissappeared, with their content now in the 1st and 3rd spines. But notice that the cleave put some data into a new field, which it has called Spine2|4. We can improve this name using the newFields argument:

example |>
  cleave(1:2, 3:4, newFields = 'Silbe') -> example

example
>    ################## vvv Reshaping_example.hum vvv ##################
>       1:         **kern**silbe       **kern**silbe          **harm
>       2:                *ICvox              *ICvox               *
>       3:                *Ialto             *Isoprn               *
>       4:                 *M4/4               *M4/4               *
>       5:                   *C:                 *C:             *C:
>       6:               4.cThis             4.eThis               I
>       7:                  8dis                8fis               .
>       8:                  4ean                4gan               .
>       9:                 4fex-               4aex-              IV
>      10:                     =                   =               =
>      11:                2g-am-             4dd-am-             I64
>      12:                     .                4cc_               .
>      13:                2g-ple              2b-ple               V
>      14:                    *-                  *-              *-
>    ################## ^^^ Reshaping_example.hum ^^^ ##################
>    
>       Data fields: 
>               *Silbe :: character
>               *Token :: character

But, wait, where did the **kern and **sible data go exactly? Well, since the **kern spines were the first spines we indicated to cleave(), that data stays in the original Token field. The other spines—which in this case are the **sible data—are put into the new field(s). See:

example |> select(Token)
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:                **kern              **kern              **harm
>       2:                *ICvox              *ICvox                   *
>       3:                *Ialto             *Isoprn                   *
>       4:                 *M4/4               *M4/4                   *
>       5:                   *C:                 *C:                 *C:
>       6:                   4.c                 4.e                   I
>       7:                    8d                  8f                   .
>       8:                    4e                  4g                   .
>       9:                    4f                  4a                  IV
>      10:                     =                   =                   =
>      11:                    2g                 4dd                 I64
>      12:                     .                 4cc                   .
>      13:                    2g                  2b                   V
>      14:                    *-                  *-                  *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>                Silbe :: character
>               *Token :: character

example |> select(Silbe)
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:               **silbe             **silbe              **harm
>       2:                *ICvox              *ICvox                   *
>       3:                *Ialto             *Isoprn                   *
>       4:                 *M4/4               *M4/4                   *
>       5:                   *C:                 *C:                 *C:
>       6:                  This                This                   .
>       7:                    is                  is                   .
>       8:                    an                  an                   .
>       9:                   ex-                 ex-                   .
>      10:                     =                   =                   =
>      11:                  -am-                -am-                   .
>      12:                     .                   _                   .
>      13:                  -ple                -ple                   .
>      14:                    *-                  *-                  *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>               *Silbe :: character
>                Token :: character

Notice that the fifth spine, which wasn’t part of the cleave, is also left untouched in the Token field.


In some datasets, there might be different numbers of **kern/**silbe spines in different files within the dataset. In these cases, it will be much smarter to pass cleave() the names of the exclusive intepretations we want to cleave. We should get the same result we got manually before:

example <- readHumdrum(humdrumRroot, 'examples/Reshaping_example.hum')

example |>
  cleave(c('kern', 'silbe'))
>    ################## vvv Reshaping_example.hum vvv ##################
>       1:         **kern**silbe       **kern**silbe          **harm
>       2:                *ICvox              *ICvox               *
>       3:                *Ialto             *Isoprn               *
>       4:                 *M4/4               *M4/4               *
>       5:                   *C:                 *C:             *C:
>       6:               4.cThis             4.eThis               I
>       7:                  8dis                8fis               .
>       8:                  4ean                4gan               .
>       9:                 4fex-               4aex-              IV
>      10:                     =                   =               =
>      11:                2g-am-             4dd-am-             I64
>      12:                     .                4cc_               .
>      13:                2g-ple              2b-ple               V
>      14:                    *-                  *-              *-
>    ################## ^^^ Reshaping_example.hum ^^^ ##################
>    
>       Data fields: 
>               *Silbe :: character
>               *Token :: character

What if we want to study the note combinations that are formed between the two **kern spines? As they are, this would be hard because the tokens from each spine are all in their own rows. However, we could cleave() together the two kern spines! (We’ll also isolate just the **kern spines for this.)

example <- readHumdrum(humdrumRroot, 'examples/Reshaping_example.hum')

example |>
  index2( , '**kern') |>
  kern(simple = TRUE) |>
  cleave(c(1, 2)) |> 
  count()
>    humdrumR count distribution 
>    Kern  Spine2                  
>               a  b  c  d  e  f  g
>      NA       .  .  1  .  .  .  .
>       c       .  .  .  .  1  .  .
>      c#       .  .  .  .  .  .  .
>       d       .  .  .  .  .  1  .
>      e-       .  .  .  .  .  .  .
>       e       .  .  .  .  .  .  1
>       f       1  .  .  .  .  .  .
>      f#       .  .  .  .  .  .  .
>       g       .  1  .  1  .  .  .
>      a-       .  .  .  .  .  .  .
>       a       .  .  .  .  .  .  .
>      b-       .  .  .  .  .  .  .
>       b       .  .  .  .  .  .  .
>               a  b  c  d  e  f  g
>    Kern  Spine2                  
>    humdrumR count distribution

Multi-cleaving

What about that **harm spine in our data. Unlike the **silbe spines, the **harm spine is not “paired up” with the **kern spines. Rather, the **harm actually describes what is happening in the entire record of data. The chords indicated in this spine are associated with the pitches in both, or either, of the **kern spines. Luckily, cleave() will handle this:

example <- readHumdrum(humdrumRroot, 'examples/Reshaping_example.hum')

example |>
  cleave(c('kern', 'harm'))
>    ################### vvv Reshaping_example.hum vvv ####################
>       1:        **kern**harm      **silbe      **kern**harm      **silbe
>       2:              *ICvox            *            *ICvox            *
>       3:              *Ialto            *           *Isoprn            *
>       4:               *M4/4            *             *M4/4            *
>       5:                 *C:            *               *C:            *
>       6:                4.cI         This              4.eI         This
>       7:                  8d           is                8f           is
>       8:                  4e           an                4g           an
>       9:                4fIV          ex-              4aIV          ex-
>      10:                   =            =                 =            =
>      11:               2gI64         -am-            4ddI64         -am-
>      12:                   .            .               4cc            _
>      13:                 2gV         -ple               2bV         -ple
>      14:                  *-           *-                *-           *-
>    ################### ^^^ Reshaping_example.hum ^^^ ####################
>    
>       Data fields: 
>               *Harm  :: character
>               *Token :: character

Data from the **harm spine is copied twice into a new field “on top of” both **kern spines!

Cleaving Stops and Paths

Though spines are the most common structure in humdrum data that you might need to “cleave” together, we can also cleave other structures. Of course, it depends on what questions you are trying to ask of your data!

Multi-Stops

Consider this example, with multi-stop chords in a **kern spine:

example_stops <- readHumdrum(humdrumRroot, 'examples/Reshaping_example2_stops.hum')

example_stops
>    ###### vvv Reshaping_example2_stops.hum vvv ######
>       1:                **kern              **harm
>       2:                 *M4/4                   *
>       3:                   *C:                 *C:
>       4:                    4c                   I
>       5:                    4B                  Vb
>       6:               4B- d g                  vb
>       7:                4A d f                 IVb
>       8:                     =                   =
>       9:                2G c e                 I64
>      10:                2G B d                   V
>      11:                    *-                  *-
>    ###### ^^^ Reshaping_example2_stops.hum ^^^ ######
>    
>       Data fields: 
>               *Token :: character

By default, humdrum\(_{\mathbb{R}}\) treats each token (note) in each stop as a separate data observation, with its own row in the humdrum table. If we are studying harmony, we might want to align those stops “on top” of each other, in different fields. But, we can cleave the stops together, putting them each into their own field!

example_stops |>
  cleave(Stop = 1:3)
>    ###### vvv Reshaping_example2_stops.hum vvv ######
>       1:                **kern              **harm
>       2:                 *M4/4                   *
>       3:                   *C:                 *C:
>       4:                    4c                   I
>       5:                    4B                  Vb
>       6:                 4B-dg                  vb
>       7:                  4Adf                 IVb
>       8:                     =                   =
>       9:                  2Gce                 I64
>      10:                  2GBd                   V
>      11:                    *-                  *-
>    ###### ^^^ Reshaping_example2_stops.hum ^^^ ######
>    
>       Data fields: 
>               *Stop2 :: character
>               *Stop3 :: character
>               *Token :: character

The first stop (Stop == 1) is left in the Token field, but we get new Stop2 and Stop3 fields holding the other stops!

Spine Paths

When working with spine paths, it is often less obvious how we should interpret different paths in terms of data observations, but if you want to cleave them, you can. Don’t forget that Path is numbered starting from 0.

example_paths <- readHumdrum(humdrumRroot, 'examples/Reshaping_example3_paths.hum')

example_paths |>
  cleave(Path = 0:1)
>    ###### vvv Reshaping_example3_paths.hum vvv ######
>       1:                **kern              **harm
>       2:                 *M4/4                   *
>       3:                   *C:                 *C:
>       4:                    4c                   I
>       5:                    4d                   .
>       6:                    4e                   .
>       7:                    *^                   *
>       8:                  4d4f                  ii
>       9:                     =                   =
>      10:                  2c2g                   I
>      11:                  2B2g                  Vb
>      12:                    *v                   *
>      13:                     =                   =
>      14:                    *-                  *-
>    ###### ^^^ Reshaping_example3_paths.hum ^^^ ######
>    
>       Data fields: 
>               *Path1 :: character
>               *Token :: character