The music class

Previous vignettes introduced the noteworthy and noteinfo classes.

Constructing a music object

First create a music object from noteworthy and notinfo objects. An object of each class can be combined to form a music-class object.

notes <- "c d e f g a b ceg~ ceg"
info <- "8*8 1"
x <- as_music(notes, info)
x
#> <Music string>
#>   Format: space-delimited time
#>   Values: c8 d8 e8 f8 g8 a8 b8 <ceg~>8 <ceg>1

as_music() offers several additional arguments. Music objects also store key signature, time signature and tempo. They include default values: key of C, 4/4 common time, and 60 half note beats per measure. However, unless you are transcribing your data to sheet music, you can probably ignore these object attributes entirely. Optionally you can also include a lyrics object.

Here is a closer look at the music object.

summary(x)
#> <Music string>
#>   Timesteps: 9 (7 notes, 2 chords)
#>   Octaves: tick
#>   Accidentals: flat
#>   Key signature: c
#>   Time signature: 4/4
#>   Tempo: 2 = 60
#>   Lyrics: NA
#>   Format: space-delimited time
#>   Values: c8 d8 e8 f8 g8 a8 b8 <ceg~>8 <ceg>1

Some of the summary information is the same as seen with noteworthy objects and more is added that is unique to music objects. Just to note, octave tick format is explicitly shown as it is with noteworthy objects, though it is the only valid format for music objects.

Deconstructing a music object

Music analysis generally requires disentangling time and sound. For this reason, building a music object from notes and note info is not directly useful for analysis. It is a convenient object structure, however.

Aside from generic methods and a handful of other functions that make sense to apply to any of these three classes, most functions in the package that operate on noteworthy strings intentionally do not accept a music object directly. This helps keep in mind that it is not ideal to continually deconstruct and reconstruct a music object just to perform operations on one of the two main component parts.

You can split a music object into a list with music_split() or you can extract the relevant piece with one of the other functions below.

music_split(x)
#> $notes
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: c d e f g a b <ceg~> <ceg>
#> 
#> $info
#> <Note info string>
#>   Format: space-delimited time
#>   Values: 8 8 8 8 8 8 8 8 1
#> 
#> $lyrics
#> [1] NA
#> 
#> $key
#> [1] "c"
#> 
#> $time
#> [1] "4/4"
#> 
#> $tempo
#> [1] "2 = 60"

music_notes(x)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: c d e f g a b <ceg~> <ceg>
music_info(x)
#> <Note info string>
#>   Format: space-delimited time
#>   Values: 8 8 8 8 8 8 8 8 1
music_key(x)
#> [1] "c"
music_time(x)
#> [1] "4/4"
music_tempo(x)
#> [1] "2 = 60"

Constructing from character

The real value is in building brand new music objects from a single character string input. This method of music object construction allows for relatively efficient data entry. There can potentially be a lot less typing involved when directly entering music syntax for a new piece of music if you do so with the music class rather than creating noteworthy and noteinfo objects separately.

This is also a good place to mention that like with noteworthy and noteinfo strings, a handy adjective can be used to check the validity of the music input.

x <- "a,4*5 b,4- c4 cgc'e'~4 cgc'e'1 e'4 c' g c ce'1"
musical(x)
#> [1] TRUE
x <- as_music(x)
x
#> <Music string>
#>   Format: space-delimited time
#>   Values: a,4 a,4 a,4 a,4 a,4 b,4- c4 <cgc'e'~>4 <cgc'e'>1 e'4 c'4 g4 c4 <ce'>1

Details

Notice that each timestep remains space-delimited (vectors are also allowed). At each timestep, the notes and the note info are bound together unambiguously with no delimiter. This is why tick octave numbering is required for music objects.

You can see that the expansion operator * is still allowed and continues to be place at the right end of a timestep.

Do not forget that tied notes indicated with the ~ are part of the note and not part of the note info! Order matters here.

You can also include rests r and silent rests s.

Other efficiencies

Another benefit to this approach for entering new data is that durations are inferred from the previous timesteps if not provided. You do not need to explicitly enter a duration value until there is a change. This is not possible with noteinfo because it could result in simply removing timesteps, but here there is always a note entry present to mark each timestep.

This one is a bit of an aside, but if string numbers are provided for a stringed and fretted instrument context (see example below), there is also no need to specify more than the starting string number (lowest pitch/highest number) unless there are non-consecutive strings. The purpose of the music class is not transcription, but to corral related data together for analysis.

However, music objects do lend themselves seamlessly to some transcription tasks, which is why string numbering is allowed even though it is not focused on. You can see an example here. It is the same as above, but string numbers are indicated following a semicolon delimiter. Like durations, string numbers also repeat silently. Any inferred repeated information across timesteps carries over rests, including string numbers. String numbers at a rest timestep are ignored gracefully.

x <- "a,4;5*5 b,4- c4 cgc'e'~4 cgc'e'1 e'4;2 c';3 g;4 c;5 ce'1;51"
x <- as_music(x)
x
#> <Music string>
#>   Format: space-delimited time
#>   Values: a,4 a,4 a,4 a,4 a,4 b,4- c4 <cgc'e'~>4 <cgc'e'>1 e'4 c'4 g4 c4 <ce'>1
music_strings(x)
#>  [1] "5"    "5"    "5"    "5"    "5"    "5"    "5"    "5432" "5432" "2"   
#> [11] "3"    "4"    "5"    "51"

Lyrics data returns as NA when requested if a lyrics object was not provided during the construction of a music object. In contrast, string number information is not represented in the object at all if not provided when constructed, since it is not relevant to the general purpose of the class and is only used for specific transcription use cases.

summary(x)
#> <Music string>
#>   Timesteps: 14 (11 notes, 3 chords)
#>   Octaves: tick
#>   Accidentals: flat
#>   Key signature: c
#>   Time signature: 4/4
#>   Tempo: 2 = 60
#>   Lyrics: NA
#>   Strings: 5 5 5 5 5 5 5 5432 5432 2...
#>   Format: space-delimited time
#>   Values: a,4 a,4 a,4 a,4 a,4 b,4- c4 <cgc'e'~>4 <cgc'e'>1 e'4 c'4 g4 c4 <ce'>1
music_split(x)
#> $notes
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: a, a, a, a, a, b, c <cgc'e'~> <cgc'e'> e' c' g c <ce'>
#> 
#> $info
#> <Note info string>
#>   Format: space-delimited time
#>   Values: 4 4 4 4 4 4- 4 4 1 4 4 4 4 1
#> 
#> $string
#>  [1] "5"    "5"    "5"    "5"    "5"    "5"    "5"    "5432" "5432" "2"   
#> [11] "3"    "4"    "5"    "51"  
#> 
#> $lyrics
#> [1] NA
#> 
#> $key
#> [1] "c"
#> 
#> $time
#> [1] "4/4"
#> 
#> $tempo
#> [1] "2 = 60"

As you can see above, the print statement does not change. But the other functions above reveal that the additional information is now contained in the music object.

Working with music objects

While many functions that perform operations on noteworthy and note info objects require those objects exclusively, meaning that you are supposed to use functions like music_notes() and music_info() to extract the given component from a music object, there are several functions that are sensible to apply directly to music objects.

First and foremost, the same generic methods are implemented for music objects. Square bracket subsetting will even subset any lyrics or string numbers that are present in the object, not just the notes and note info.

tail(x)
#> <Music string>
#>   Format: space-delimited time
#>   Values: <cgc'e'>1 e'4 c'4 g4 c4 <ce'>1
x[8:9]
#> <Music string>
#>   Format: space-delimited time
#>   Values: <cgc'e'~>4 <cgc'e'>1
y <- rep(x[9:10], each = 2)
y
#> <Music string>
#>   Format: space-delimited time
#>   Values: <cgc'e'>1 <cgc'e'>1 e'4 e'4
music_strings(y)
#> [1] "5432" "5432" "2"    "2"

There are several time functions that work nicely on music objects. They were not introduced in the vignette on note info because they are more relevant here. Most require the level of information contained in a music object because they deal in real time such as seconds. This works with music objects because they contain a tempo value. Note info objects do not contain this level of information. They can be passed to functions that require a tempo value, as long as you also pass a tempo to any function with a tempo argument.

The example below is intended to be complex, including many changes in note duration and other note info. There is not much to be gained by writing it as a single string in this case. For clarity, it starts from separate notes and note info. See the help documentation for more examples and details.

a <- notate("t8x", "Start here")
notes <- "a, b, c d e f g# a r ac'e' a c' e' c' r*3 ac'e'~ ac'e'"
info <- paste(a, "t8x t8-. 16 4.. 16- 16 2^ 2 4. 8( 4)( 4) 8*4 1 1")
x <- as_music(notes, info)

n_measures(x)
#> [1] 5.375
n_beats(x)
#> [1] 21.5
bpm(x)
#> [1] 120

seconds(x)
#> [1] 10.75
steps_per_measure(x)
#> # A tibble: 5 × 2
#>   measure steps
#>     <int> <int>
#> 1       1     8
#> 2       2     2
#> 3       3     4
#> 4       4     4
#> 5       5     1
seconds_per_measure(x)
#> [1] 2
seconds_per_step(x)
#>  [1] 0.1666667 0.1666667 0.1666667 0.1250000 0.8750000 0.1250000 0.1250000
#>  [8] 1.0000000 1.0000000 0.7500000 0.2500000 0.5000000 0.5000000 0.2500000
#> [15] 0.2500000 0.2500000 0.2500000 2.0000000 2.0000000
steps_start_time(x)
#>  [1] 0.0000000 0.1666667 0.3333333 0.5000000 0.6250000 1.5000000 1.6250000
#>  [8] 1.7500000 2.7500000 3.7500000 4.5000000 4.7500000 5.2500000 5.7500000
#> [15] 6.0000000 6.2500000 6.5000000 6.7500000 8.7500000

You may notice above that the table returned by steps_per_measure() containing the number of timesteps per measure of music does not have an entry for measure six. There is a measure six. You can see from n_measures() being greater than five that a sixth measure does begin even if the duration of all the time steps does not yield a complete sixth measure. The reason there is no entry in the table is because this measure has no timesteps. The final chord in the sequence lasts for a whole measure, beginning in measure five and carrying over to six, but no timestep starts in six.

Combining note info with a tempo is powerful because it allows you to move from beats into real time. The music object is a convenient structure for keeping sound and time together and splitting and extracting parts of the object for independent computations is easy when using the various supporting functions.