Code Monkey home page Code Monkey logo

tabr's Introduction

tabr

Project Status: Active – The project has reached a stable, usable state and is being actively developed. R-CMD-check Codecov test coverage CRAN status CRAN downloads Github Stars

Overview

The tabr package provides a music notation syntax and a collection of music programming functions for generating, manipulating, organizing and analyzing musical information in R.

The music notation framework facilitates creating and analyzing music data in notation form; i.e, more from the perspective and in the language of a musician than, say, an audio engineer.

Citation

Matthew Leonawicz (2023). tabr: Music Notation Syntax, Manipulation, Analysis and Transcription in R. R package version 0.4.9. https://CRAN.R-project.org/package=tabr

Contribute

Contributions are welcome. Contribute through GitHub via pull request. Please create an issue first if it is regarding any substantive feature add or change.

Installation

Install the CRAN release of tabr with

install.packages("tabr")

Install the development version from GitHub with

# install.packages("remotes")
remotes::install_github("leonawicz/tabr")

Motivating example

as_music("r8 c d e f g a b c'1") |> plot_music_guitar()

Music data structures

It’s easiest to begin with a high level view using some basic examples.

Music data can be viewed, manipulated and analyzed while in different forms of representation based around different data structures: strings and data frames. Each representation offers advantages over the other for different use cases.

Music syntax can be entered directly and represented in character strings to minimize the formatting overhead of data entry by using simple data structures, for example when wanting to quickly enter and transcribe short pieces of music syntax in R into sheet music or tablature files. You can also enter sound and time together with the music class, and no need to repeat consecutive durations until a change.

Here is an example of making a string of pitches noteworthy and a string of combined pitch and duration into a music object. Such objects carry various musical information based on the syntax of an input string.

library(tabr)

x <- "a, c e g# a ac'e' ac'e'~ ac'e' a c' e' a'"
x <- as_noteworthy(x)
x
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: a, c e g# a <ac'e'> <ac'e'~> <ac'e'> a c' e' a'

summary(x)
#> <Noteworthy string>
#>   Timesteps: 12 (9 notes, 3 chords)
#>   Octaves: tick
#>   Accidentals: sharp
#>   Format: space-delimited time
#>   Values: a, c e g# a <ac'e'> <ac'e'~> <ac'e'> a c' e' a'

y <- "a,8 c et8 g# a ac'e'4. ac'e'~8 ac'e'4 at4 c' e' a'1"
y <- as_music(y)
summary(y)
#> <Music string>
#>   Timesteps: 12 (9 notes, 3 chords)
#>   Octaves: tick
#>   Accidentals: sharp
#>   Key signature: c
#>   Time signature: 4/4
#>   Tempo: 2 = 60
#>   Lyrics: NA
#>   Format: space-delimited time
#>   Values: a,8 c8 et8 g#t8 at8 <ac'e'>4. <ac'e'~>8 <ac'e'>4 at4 c't4 e't4 a'1

music_split(y)
#> $notes
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: a, c e g# a <ac'e'> <ac'e'~> <ac'e'> a c' e' a'
#> 
#> $info
#> <Note info string>
#>   Format: space-delimited time
#>   Values: 8 8 t8 t8 t8 4. 8 4 t4 t4 t4 1
#> 
#> $lyrics
#> [1] NA
#> 
#> $key
#> [1] "c"
#> 
#> $time
#> [1] "4/4"
#> 
#> $tempo
#> [1] "2 = 60"

Functions exist for directly performing various mathematical, logical and organizational operations and musical transformations on strings like the one above by checking their music syntax validity and adding custom classes and methods to these strings (more on this below). tabr offers special object classes that facilitate working with music data and notation in ways that are natural to R, robust, tidy, and lend themselves well to transcription as well as analysis.

Of course, none of this will work on character strings that are not “noteworthy” or “musical”, for example. Invalid, unworthy syntax is rejected early with an error, preventing corrupted music syntax from causing unexpected issues later on.

The same music data can also be organized in tidy data frames, allowing for a more familiar and powerful approach to the analysis of large amounts of structured music data.

x <- "a,8 c e r r c a, g#, a ac'e'"
as_music(x) |> as_music_df()
#> # A tibble: 10 × 14
#>    duration pitch note  semitone octave  freq pitch_int scale_int slur  slide
#>    <chr>    <chr> <chr>    <int>  <int> <dbl>     <int> <chr>     <chr> <lgl>
#>  1 8        a,    a           57      2  110.        NA <NA>      <NA>  FALSE
#>  2 8        c     c           48      3  131.         3 m3        <NA>  FALSE
#>  3 8        e     e           52      3  165.         4 M3        <NA>  FALSE
#>  4 8        r     r           NA     NA   NA         NA <NA>      <NA>  FALSE
#>  5 8        r     r           NA     NA   NA         NA <NA>      <NA>  FALSE
#>  6 8        c     c           48      3  131.        -4 M3        <NA>  FALSE
#>  7 8        a,    a           57      2  110.        -3 m3        <NA>  FALSE
#>  8 8        g#,   g#          56      2  104.        -1 m2        <NA>  FALSE
#>  9 8        a     a           57      3  220         13 m9        <NA>  FALSE
#> 10 8        ac'e' ace         57      3  220          0 P1        <NA>  FALSE
#> # ℹ 4 more variables: bend <lgl>, dotted <int>, articulation <chr>,
#> #   annotation <chr>

Several functions are available for mapping seamlessly between and manipulating these data structures and their representations of musical information.

Noteworthy strings

It is helpful to have a deeper understanding of how this music notation syntax informs data structures and operations integrated throughout the package.

As a quick introduction and to get oriented to the music notation syntax offered by tabr, consider the concept of a noteworthy string. This is like any other character string, except that what makes a string noteworthy is that its content consists strictly of valid tabr music notation syntax. It can be parsed unambiguously and meaningfully as input by package functions that inspect and manipulate musical information.

A bit about basic syntax

A simple character string like "c e g", or alternatively as a vector, c("c", "e", "g"), is a noteworthy string. The single lowercase letter "a" is noteworthy. So are "a_" and "a#" (flat and sharp). However, "A" is not (case sensitivity), nor is "z". Of course, as seen above, there is more valid syntax than just the lowercase musical note letters a through g and sharp and flat notation.

An important piece of syntax is the octave. In conjunction with a note, specifying a unique pitch requires the octave number, either in tick format (comma and single quote, c, c c') or integer format (c2 c c4). Octave 3 is the implicit default; there is no tick in tick format and explicitly adding the 3 in integer format is unnecessary. The pitches c d e f g a b (default octave) are the notes in the octave below middle C (c').

You’ve already seen above with the example using a music object that a noteworthy string can be one part of a more complete piece of musical information. Tick format is necessary to avoid ambiguity with respect to temporal information once two such pieces of information are merged together. For this reason, tick format is preferred in general. Tick format also matches that used by the LilyPond music engraving software, which is used by the tabr LilyPond API for anything transcription related like rendering sheet music to PDF or in R markdown documents.

For all available syntax specifications and related details see the package vignettes.

The noteworthy class

Noteworthiness can be checked on any character string. When defining noteworthy strings you can define them like any other character vector. However, you will notice that package functions that operate on noteworthy strings and whose output is another noteworthy string will yield a string with the supplemental noteworthy class. This has its own print() and summary() methods.

Several other generic methods are also implemented for the noteworthy class, making it easy to perform simple but powerful operations on these objects in a familiar way. While many functions will attempt to coerce a string to noteworthy, not all will and some methods are implemented specifically for the class.

x <- "g#, c d# g#c'd#'"
as_noteworthy(x)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: g#, c d# <g#c'd#'>

is_note(x)
#> [1]  TRUE  TRUE  TRUE FALSE
is_chord(x)
#> [1] FALSE FALSE FALSE  TRUE
chord_is_major(x)
#> [1]   NA   NA   NA TRUE
(x <- transpose(x, 1))
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: a, c# e <ac#'e'>

summary(x)
#> <Noteworthy string>
#>   Timesteps: 4 (3 notes, 1 chord)
#>   Octaves: tick
#>   Accidentals: sharp
#>   Format: space-delimited time
#>   Values: a, c# e <ac#'e'>

distinct_pitches(x)
#> <Noteworthy string>
#>   Format: space-delimited time
#>   Values: a, c# e a c#' e'
distinct_pitches(x) |> pitch_freq() # in Hz
#> [1] 110.0000 138.5913 164.8138 220.0000 277.1826 329.6276

These are just a few examples. There are many more functions in tabr that intuitively operate on noteworthy strings, abstracting the interpretation of relatively simple symbolic text specifications as quantitative and/or structured musical information.

There is also a noteinfo class, and a music class (seen above). You can learn more in the vignettes. The music class is particularly useful for efficient music data entry and basic transcription tasks.

Tidy music analysis

Ideally music data already exists in a data frame format. But if it doesn’t, or if you just wrote out a new note sequence like below, getting this data into a data frame for a more tidy approach to analysis is easy. Conversion can also populate several derivative variables in the process.

In the earlier example you saw the result of calling as_music_df() on a noteworthy string.

x <- "a, c e r r c a, g#, a ac'e'"
as_music_df(x)

You may have noticed that rests (r) are allowed for timesteps and that functions that compute lagged intervals respect these gaps. Since as_music_df() was only provided with a string of pitches, there are no explicit time variables in the data frame. However, discrete timesteps still exist and they do not have to contain notes.

There are a number of derivative columns. If you are working with a large sequence of music, there is no need to carry all of these variables along through your analysis if you do not need them. They can be created using various package functions and you can build onto your data frame and transform variables later with a function like mutate() from dplyr.

library(dplyr)
x <- "a, c e r r c a, g#, a ac'e'"
tibble(pitch = as_vector_time(x)) |> 
  mutate(scale_int = scale_diff(pitch))
#> # A tibble: 10 × 2
#>    pitch      scale_int
#>    <notwrthy> <chr>    
#>  1 a,         <NA>     
#>  2 c          m3       
#>  3 e          M3       
#>  4 r          <NA>     
#>  5 r          <NA>     
#>  6 c          M3       
#>  7 a,         m3       
#>  8 g#,        m2       
#>  9 a          m9       
#> 10 ac'e'      P1

In fact, it’s much more powerful to create the columns according to your needs using specific functions and their various arguments. But as_music_df() is convenient and also offers some additional arguments. Adding key and scale allows for scale degrees. scale is diatonic by default but does not have to be.

x <- "g g#"
as_music_df(x, key = "am") |> 
  select(pitch, key, scale, scale_deg)
#> # A tibble: 2 × 4
#>   pitch key   scale    scale_deg
#>   <chr> <chr> <chr>        <int>
#> 1 g     am    diatonic         7
#> 2 g#    am    diatonic        NA

as_music_df(x, key = "am", scale = "harmonic_minor") |> 
  select(pitch, key, scale, scale_deg)
#> # A tibble: 2 × 4
#>   pitch key   scale          scale_deg
#>   <chr> <chr> <chr>              <int>
#> 1 g     am    harmonic_minor        NA
#> 2 g#    am    harmonic_minor         7

tabr offers many functions for manipulating and analyzing music data and working in music notation. See the collection of vignettes for more information on music programming and analysis.

Transcription

Music programming in the notation syntax provided by tabr can be used for a variety of purposes, but it also integrates cohesively with the package’s transcription functions. The package also provides API wrapper functions for transcribing music notation in R into basic sheet music and guitar tablature (“tabs”) using LilyPond.

LilyPond is an open source music engraving program for generating high quality sheet music based on markup syntax. tabr generates LilyPond files from R code and can pass them to LilyPond to be rendered into sheet music pdf files. While LilyPond caters to sheet music in general and tabr can be used to create basic sheet music, the transcription functions focus on leveraging LilyPond specifically for creating quality guitar tablature. You do not need to use it for guitar tablature, but for vocal or other instrument tracks, you can change settings, such as suppressing a tab staff from your sheet music.

While LilyPond is listed as a system requirement for tabr, you can use the package for music analysis without installing LilyPond if you do not intend to render tabs. You can even use the lilypond() function to write LilyPond files to disk without the software installed, since this is only a case of R writing plain text files in the proper format. The only functions in the package that require a LilyPond installation are tab(), midily(), miditab() and any render_* functions.

Use case considerations

tabr offers a useful but limited LilyPond API and is not intended to access all LilyPond functionality from R, nor is transcription via the API the entire scope of tabr. If you are only creating sheet music on a case by case basis, write your own LilyPond files manually. There is no need to use tabr or limit yourself to its existing LilyPond API or its guitar tablature focus.

However, if you are generating music notation programmatically, tabr provides the ability to do so in R and offers the added benefit of converting what you write in R code to the LilyPond file format to be rendered as printable sheet music.

With ongoing development, the music programming side of tabr will continue to grow much more than the transcription functionality. While transcription represents about half of this introduction, this is only to provide a thorough context and overview of features. Transcription is a wonderful option to have fully integrated into the package, but it is an optional use case, and working with music data is the broader primary thrust of the package.

Why LilyPond for transcription?

LilyPond is an exceptional sheet music engraving program.

  • It produces professional, high quality output.
  • It is open source.
  • It offers a command line access point for a programmatic approach to music notation.
  • It is developed and utilized by a large community.
  • Most GUI-based applications are WYSIWYG and force a greater limitation on what you can do and what it will look like after you do it. It is only for the better that tabr is the bottleneck in transcription limitations rather than the music engraving software it wraps around.

Transcription functionality and support

The tabr package offers the following for transcription:

  • Render guitar tablature and sheet music to pdf or png.
  • Create and insert sheet music snippets directly into R Markdown documents.
  • Write accompanying MIDI files that can respect repeat notation and transposition in the sheet music (under reasonable conditions).
  • Support tablature for other string instruments besides guitar such as bass or banjo.
  • Support for instruments with different numbers of strings.
  • Support for arbitrary instrument tuning.
  • Offers inclusion (or exclusion) of formal music staves above tab staves, such as treble and bass clef staves for complete rhythm and timing information.
  • If music staff is included, the tab staff can be suppressed, e.g., for vocal tracks.
  • Track-specific setup for features like instrument type, tuning and supplemental music staves.
  • Provides common notation such as slide, bend, hammer on, pull off, slur, tie, staccato, dotted notes, visible and silent rests.
  • Allows arbitrary tuplet structure.
  • Above-staff text annotation.
  • Percent and volta repeat section notation.
  • Note transposition.
  • Staff transposition.
  • Multiple voices per track and multiple tracks per score.
  • Chord symbols above staff
  • Chord fretboard diagrams and chord chart at top of score.
  • A variety of layout control options covering settings from score attributions to font size.
  • Optional alternative input format allowing the user to provide string/fret combinations (along with key signature and instrument tuning) to map to pitch.

Basic transcription example

Rendering sheet music is based on building up pieces of musical information culminating in a score. The fundamental object to consider in the transcription context is a phrase. A phrase is created from a noteworthy string and incorporates additional information, most importantly time and rhythm. It can also include positional information such as the instrument string on which a note is played. Outside of rendering tabs, there is no reason to construct phrase objects. Everything from the phrase object on up is about using the R to LilyPond pipeline to render some kind of sheet music document.

If you are doing music analysis on noteworthy strings and are combining the note, pitch or chord information with time, that can be done with a corresponding variable; using a phrase object is not the way to do that because phrase objects are intended for the construction of LilyPond markup syntax.

As a brief example, recreate the tablature shown in the image above (minus the R logo). Here are the steps.

  • Define a musical phrase with phrase() or the shorthand alias p().
  • Add the phrase to a track().
  • Add the track to a score().
  • Render the score to pdf with tab() or another render_* function.

The code is shown below, but first some context.

Constructing a musical phrase

The term phrase here simply means any arbitrary piece of musical structure you string together. phrase() takes three main arguments when building a phrase from its component parts. The first gives pitches (or rests) separated in time by spaces. For chords, remove spaces to indicate simultaneous notes. For example, a rest followed by a sequence of pitches might be notes = "r a, c f d a f".

info is note metadata such as duration. Whole notes are given by 1, half notes by 2, quarter notes 4, and so on, e.g., info = "4 8 8 8 8 8 8" (or shorten to info = "4 8*6"). This example does not require additional information such as dotted notes, staccato notes, ties/slurs, slides, bends, hammer ons and pull offs, etc.

The third argument, string only applies to fretted string instruments and is always optional. Providing this information in conjunction with the pitch fixes the frets so that LilyPond does not have to guess them. This only applies for tablature output. Explicit string numbers are not needed for this example since lowest fret numbers (LilyPond default) are intended.

p("r a, c f d a f", "4 8*6")
#> <Musical phrase>
#> r4 <a,>8 <c>8 <f>8 <d>8 <a>8 <f>8

Building a phrase from component parts may be necessary in some programmatic contexts. However, when doing manual data entry for simple, interactive examples, the music class offers a higher level of abstraction, sparing you some typing as well as cognitive load.

Music syntax

As an aside, if you are working with the music class, you can enter notes, note info, and optionally string numbers if applicable, all in one string. This is more efficient for data entry. It can also be easier to follow because it binds the otherwise separate arguments by timestep. See the vignettes and help documentation on music objects for more details.

If you define the music object

as_music("r4 a,8 c f d a f")
#> <Music string>
#>   Format: space-delimited time
#>   Values: r4 a,8 c8 f8 d8 a8 f8

it can be passed directly to phrase(), which understands this syntax and interprets the notes argument as music syntax if the info argument is not provided (info = NULL). In fact, the music object does not even need to be previously defined. The string format can be directly provided to phrase().

(p1 <- p("r4 a,8 c f d a f"))
#> <Musical phrase>
#> r4 <a,>8 <c>8 <f>8 <d>8 <a>8 <f>8

Notice how each timestep is complete within the single character string above. Also, durations (and string numbers) can repeat implicitly until an explicit change occurs.

Score metadata and accessing LilyPond

Finally, specify some score metadata: the key signature, time signature and tempo.

If LilyPond is installed on your system (and added to your system path variable on Windows systems), tab() or any of the render_* functions should call it successfully. Windows users are recommended to just add LilyPond’s bin directory to the system path. This will take care of LilyPond as well as its bundled Python and MIDI support. As an example for Windows users, if for example the LilyPond executable is at C:/lilypond-2.24.2/bin/lilypond.exe, then add C:/lilypond-2.24.2/bin to the system path.

Minimal R code example

p1 |> track() |> score() |>
  tab("phrase.pdf", key = "dm", time = "4/4", tempo = "4 = 120")
#> #### Engraving score to phrase.pdf ####
#> GNU LilyPond 2.24.2 (running Guile 2.2)
#> Processing `./phrase.ly'
#> Parsing...
#> Interpreting music...
#> Preprocessing graphical objects...
#> Interpreting music...
#> MIDI output to `./phrase.mid'...
#> Finding the ideal number of pages...
#> Fitting music on 1 page...
#> Drawing systems...
#> Converting to `./phrase.pdf'...
#> Success: compilation successfully completed

The pdf output looks like this:

MIDI support

The package offers nominal MIDI file output support in conjunction with rendering sheet music. MIDI file writing is still handled by LilyPond, which means it must be based on a valid LilyPond file output created by tabr.

You can read MIDI files into R. This support relies on the tuneR package to read MIDI files and attempts to structure the MIDI data to integrate as best as possible with the data structures and functionality found throughout tabr.

An existing MIDI file can also be passed through directly to LilyPond to attempt to create sheet music from the MIDI file if possible, using one of LilyPond’s command line utilities for MIDI to LilyPond conversion followed by rendering the generated LilyPond file to sheet music.

References and resources

There are several vignette tutorials and examples at the tabr website.

R-Music

R for music data extraction and analysis

See the R-Music organization on GitHub for more R packages related to music data extraction and analysis.
The R-Music blog provides package introductions and examples.

Other packages

  • The tuneR package for Analysis of Music and Speech by Uwe Ligges, Sebastian Krey, Olaf Mersmann, Sarah Schnackenberg, and others.

Please note that the tabr project is released with a Contributor Code of Conduct. By contributing to this project, you agree to abide by its terms.

tabr's People

Contributors

hanoostdijk avatar leonawicz avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

tabr's Issues

Mystery of the missing 7th scale degree

> scale_note(1:8,key="e",scale="major")
<Noteworthy string>
  Format: vectorized time
  Values: e f# g# a b c# e
> scale_note(1:9,key="e",scale="major")
<Noteworthy string>
  Format: vectorized time
  Values: e f# g# a b c# e f#

I can't help but wonder what happened to the 7th scale degree (D#).

> scale_note(1:9,key="c",scale="major")
<Noteworthy string>
  Format: vectorized time
  Values: c d e f g a c d

Same issue here. I would expect a "b".

This occurred in R 3.6.2 (x64), installed today, along with tabr and its dependencies.

sf_phrase improvements

While I respect your views on the importance of composing with musical notes, I still feel that sf_phrase is an important expedient, especially when writing in various tunings and positions. It can help me maintain creative flow without having to subject my ideas to theoretical analysis as they appear. It also can lower the barrier to entry for those who do not read music, which IMO can only good for tabr.

That said, the requirements of string/fret dual-entry concept seem to undermine the benefits of expediency for me, so I have a few suggestions that I feel would improve this.

  1. Allow r,s and ~ symbols from one field to override the other.
    I feel that the improved writability justifies the potential confusion.
    sfp("4*3", "0 2~ 2", "8. 16 2.")
    sfp("4 4~ 4", "0 2 2", "8. 16 2.")
    sfp("4*3", "0 r 2", "8. 16 2.")
    sfp("4 r 3", "0 2 2", "8. 16 2.")

  2. provide the option to do away with string, using positional string assignment.
    x indicates muted strings.
    For tokens less than six characters, infer muted top
    sf_phrase("x02030 2x0232 354","2 2 1")

  3. specify the starting string.
    allow these assignments to carry on to subsequent tokens
    sf_phrase("3s987 775 553 232 5s757 545 325 6s2210","4")
    equivalent to:
    phrase <- sf_phrase("3s*4 543*3 6543","987 775 553 232 757 545 325 2210","4")

  4. inline info tokens
    let these also carry
    sf_phrase("21","87-16*4 75-8 53 32")
    equivalent to:
    sf_phrase("21","87*4 75","16*4 8*3")

  5. allow hex-like fret numbers
    sf_phrase("6543","0a9b","4")
    equivalent to:
    sf_phrase("6543","0(10)9(11)","4")

  6. put them all together
    sf_phrase("3sa8~7~-8( 987-8) 775-4 5xx553*2 3s232-16*4 5s757-4r 545-4 325 6s22x0")
    equivalent to:
    phrase = sf_phrase("3~2~1 321*2 6321*2 321*4 r 543*2 653 ","(10)~8~7 987 775 5553*2 232*4 r 545 235 220","8( 8) 4*3 16*4 4*4")

Issue with package in MacOS

When I run one of the basic examples provided in the documentation:

library(tabr)
p1 <- p("r a2 c f d a f", "4 8*6")
track1 <- track(p1)
song <- score(track1)
tab(song, "phrase.pdf", key = "dm", time = "4/4", tempo = "4 = 120")

I get:

#### Engraving score to phrase.pdf ####
arguments 'show.output.on.console', 'minimized' and 'invisible' are for Windows only
unknown option --pdf
usage:  [option] ... [-c cmd | -m mod | file | -] [arg] ...
Try `python -h' for more information.

This is very likely to be due to the fact that I'm using MacOS (Darwin, to be more specific) and not Windows since the error is being generated by the options that are for a Windows OS. I tried changing Lilypond's path, but that didn't help. What could be the next steps for us to figure how to solve this problem?

example code works on OS X!

i saw this in the README

It has not yet been tested on Mac.

so i wanted to let you know this example worked great! i loaded things up in RStudio, and it spit out the phrase.pdf as expected.

also, i've been learning more R and have looked around for examples of well structured packages to learn from. you've got quite a few, so thanks for all the code!!

Volta renders wrong

> x %>% volta(2,c(e1,e2))
<Musical phrase>
\repeat volta 3 { <c\5>4 <e\4 c'\3 g'\2>4 <e\4 c'\3 g'\2>2 | }
\alternative {
  { <a\5>1 <b\5>1 | }
}

Looks like the extra set of brackets in \alternative are causing the volta to render improperly

Q: how to create end bar?

I am trying to insert an end bar with (as an example)
voice1 <- pc(p("a3 a3 e4 e4","4 8 4 8",string=NULL),'\\bar "|."' )

With lilypond this generates (note the line split (CRLF) after the |) :

melodyA = {
\global
\override StringNumber #'transparent = ##t
a4 a8 e'4 e'8 \bar "|
."
}

and this does not create the end bar. After removing the CRLF from the .ly file the end bar shows up. Any suggestions to let this work in one go?

NB I think the split is caused by lilypond.R line 287/315 but do not know if this can easily be removed. E.g. 287 :
x <- paste0(x0, "\\override StringNumber #'transparent = ##t\n ",
gsub("\n\n", "\n", gsub("\\|", "\\|\n", x)), "}\n\n",
collapse = "\n")

S/f mismatch with 2-digit fret syntax

> sf_phrase("321", "(13)(13)5", "4")
<Musical phrase>
<gis'\3 c''\2 a'\1>4> 
> sf_phrase("321", "(13)5(13)", "4")
Error: String/fret mismatch.
In addition: Warning message:
In function_list[[k]](value) : NAs introduced by coercion
> sf_phrase("321", "5(13)(13)", "4")
Error: String/fret mismatch.
In addition: Warning message:
In function_list[[k]](value) : NAs introduced by coercion

using 0.3.1

Integrating tabr output into greater lilypond score

Hello,

I'd like your input on how to integrate output from tabr into a greater lilypond score.

I can't simply include the .ly file in another, because it will render itself when it is included.

Maybe you could add an argument to lilypond, or a separate function, which only generates the staves without calling \score or defining the header. These staves could have variable names based on the r source file name, so the output of multiple tabr projects could be combined.

Or is there a better solution?

It would also be nice to be able to include lilypond files and code in the r source, although I'm not sure exactly how that would work.

(some) paper specifications do not work

Working with version 0.3.9.9000 of tabr and version 2.19.83 of LilyPond on Windows 10.

The size paper specifications do not seem to work:
paper <- list(textheight = 55,linewidth = 120) has no effect.
I got it working by adapting the generated .ly file:

  • moving the paper block from the end of the .ly file to the front
  • inserting the line #(set! paper-alist (cons '("my size" . (cons (* 120 mm) (* 55 mm))) paper-alist)) as first line in the .ly file
  • replacing the size parameters in the paper block by #(set-paper-size \"my size\")

See function edit_ly_file in blog entry.

I also saw that print-first-page-number is handled identical to print-page-number and derived from paper$page_numbers.

I am impressed by the amount and quality of the work done on tabr and Lilypond.

Vectorized sfp input

Hello,

Following your suggestions in #15 , I have made a new input method prototype. It would be great to have your feedback and suggestions how to integrate this into the codebase.

As a composer, my ultimate goal is to type down lyrics, melodies, chords and pick patterns with a guitar on my lap. This is why I hope to add these high-level constructs into the syntax, so I don't have to distract myself writing R and tabr functions while ideas are flowing. The syntax should be readable (playable) from the command line, while also expressing complex and repetitive patterns with minimum typing overhead.

String, fret and info can be declared as vectors using [ ], which are recycled. Also, each vector can be multiplied with *.

Slurs, ties and rests are handled a bit differently to facilitate recycling. If chars like these are in either string or fret, they will override any other input for that timestep.

I'm still using alphanumeric fret input.

Delimiters work a little differently. If one delimiter is present, it assumes fret;info. Empty fields can be used anywhere; for example ;;8 will repeat the previous string and fret with a new info.

Whitespace can be added for readability.

The string-position of frets is determined by the lowest string declared in the string vector. For example, [6 5 4 3];0221; will be an E major, while [4 3 2 1];0221; is an A minor. This might be too confusing, I'm not sure. But it works well in some cases.

Adding an s after a string number should usually work, skipping over any x's in fret.

I added global vars for tuning and key, since I didn't like having to add them to every function call. There's probably a better method for this.

library(gsubfn)
library(tabr)

globalTuning <- "standard"
globalKey <- "c"

sfpv <- function(pstr, tuning = globalTuning, key=globalKey) {
    pstr <- pstr %>% paste(collapse=" ")
    pstr <- gsub("\n"," ",pstr)
    lastValues = c("6s","0","4")
                                        #expand outer mults
    pstr <- gsubfn("\\[([^\\[|\\]]*)\\]\\*([0-9]*)",
                   function(x,set,mult) {
                       ret <- paste(rep(set,mult),collapse=" ")
                       return(paste0('[',ret,']'))
                   },
                   pstr,2)
                                        #expand  inner mults
    pstr <- gsubfn("([0-9|a-s|\\.]*)\\*([0-9]*)",
                   function(x,token,mult) { return(paste(rep(token,mult),collapse=","))},
                   pstr, 2)
    pstr <- gsubfn("\\[[^\\[\\]]*\\]", function(x) gsub(" ",",",x), pstr)
    pstr <- gsub("\\[|\\]","",pstr)
    tokens <- strsplit(pstr," ")[[1]]
                                        #ignore whitespace
    tokens <- tokens[tokens!=""]
    for(tok in tokens){
        tok <- gsub(";$",";;",tok)          #hack to add an empty info string
        fields <- strsplit(tok,";")[[1]]
                                        #if it has one delimiter, assume fret and info, and use lastString     
        if(length(fields) == 2) {
            fields[3] <- fields[2]
            fields[2] <- fields[1]
            fields[1] <- lastValues[1]
        }
                                        #if it has no delimiters, assume fret and use lastString and lastInfo
        else if(length(fields) == 1) {
            fields[2] <- fields[1]
            fields[1] <- lastValues[1]
            fields[3] <- lastValues[3]
        }
                                        #if any field is empty, replace it from lastValues
        matches <- grep("^$",fields)
        fields <- replace(fields,matches,lastValues[matches])
        lastValues <- fields
                                        #unpack and recycle sequences as a matrix
        subLists <- lapply(fields, function(x) strsplit(x,",")) %>% unlist(recursive=FALSE)
        fr <- do.call(rbind,subLists)
                                        #frets are assigned relative to the lowest string called for in the token
        lowestString <- strsplit(gsub("[^0-9|x]","",fr[1,]),"") %>% unlist %>% max %>% as.numeric
                                        #convert timesteps to tabr syntax
        fr <- apply(fr, 2, function(x) {
            str  <- x[1]
            fret <- x[2]
            inf  <- x[3]
            isRest <- FALSE
                                        #break if timestep is a rest
            rst <- regmatches(fret,regexpr("^r$|^s$",fret))
            if(length(rst) > 0) {
                str <- fret
                isRest <- TRUE
            }
            rst <- regmatches(str,regexpr("^r$|^s$",str))
            if(length(rst) > 0) {
                fret <- str
                isRest <- TRUE
            }
            if(isRest) { return(c(str,fret,inf)) }

                                        #handle ties, which must be shared between str & fret
            tie <- ""
            for(field in c(str,fret,inf)) {
                if(length(grep("~",field))  > 0) {
                    tie <- "~"
                }
            }
                                        #save other metachars from string and fret
            strMeta <- (gsub("[0-9|s|~]","",str) %>% strsplit(""))[[1]]
            fretMeta <- (gsub("[0-9|a-q|x|~]","",fret) %>% strsplit(""))[[1]]
            meta <- c(strMeta,fretMeta) %>% unique
                                        #vectorize str and fret
            if(length(grep("[0-9]+s",str)) > 0) {
                str <- as.numeric(gsub("[^0-9]","",str)):1
            }
            else {
                str <- strsplit( gsub("[^0-9]","",str), "")[[1]] %>% as.numeric
            }
            fret <- strsplit(gsub("[^0-9|a-q|x]","",fret),"")[[1]]
                                        #trim string and fret
            strIndex <- lowestString-str+1
            keep <- intersect(strIndex, grep("[^x]",fret))
                                        #repack fields with ties and meta
            fret <- fret[keep] %>% paste0(tie,collapse="")
            str <- (lowestString:1)[keep] %>% paste0(tie,collapse="")
            inf <- c(inf,meta) %>% paste(collapse="")
            return(c(str,fret,inf))
        })
                                        #convert alphanumeric frets to tabr syntax
        hex <- c("a",    "b",    "c",    "d",    "e",    "f",    "g",    "h",    "i",    "j",    "k",    "l",
                 "m",    "n",    "o",    "p",    "q")
        num <- c("(10)", "(11)", "(12)", "(13)", "(14)", "(15)", "(16)", "(17)", "(18)", "(19)", "(20)", "(21)",
                 "(22)", "(23)", "(24)", "(25)", "(26)")
        fr[2,] <- Reduce(function(d, i) gsub(hex[i], num[i], d), seq_along(hex), fr[2,])
        newPhrase <- sf_phrase(fr[1,],fr[2,],fr[3,],tuning=tuning,key=key)
        if(!exists("retString")) { retString <- newPhrase }
        else { retString <- c(retString,newPhrase) }
    }
    return(retString)
}

Here is an example. I think it's pretty compact while still being somewhat readable. Syntax highlighting would also help with this.

library(tabr)
source("sfpv.r")

globalTuning <- "f#,b,f#ac#'f#'"
globalKey    <- "sharp"

guit <- c(
    sfpv("[6 5 3 4 5]*4;3320;[4 16*2 4 8] 5530") %>% rep(2),
    sfpv("[6 5 3 4 5]*2;3320;[4 16*2 4 8] 3320 0020 1330 3320 [5 4 2 3 4];0320;"),
    sfpv("[5 4 2 3];0320;[4 16*2 4] 4;0;8 [5 4 2 3 4]*2;3020;[4 16*2 4 8]"),
    sfpv("[6 5 4 3 5]*4;0950;[4 16*2 4 8]"),
    sfpv("[6 5 4 3];7760;[4 16*2 4] 5;9;8") %>% rep(4),
    sfpv("[6 5 4];560;[4 16*2] [5 3];9x0;[4 8]") %>% rep(4),
    sfpv("[6 5 4 3 4]*4;3554;[4 16*2 4 8] 2654"),
    sfpv("[6 5 3 4 5]*4;3320;[4 16*2 4 8] 5530") %>% rep(2),
    sfpv("[6 5 3 4 5]*2;3320;[4 16*2 4 8] 3320 0020 1330 3320 [5 4 2 3 4];0320;"),
    sfpv("[5 4 2 3];0320;[4 16*2 4] 4;0;8 [5 4 2 3 4]*2;3020;[4 16*2 4 8]"),
    sfpv("[5 2 3(];0221;[4 16*2] [3) 4];20;[4 8]") %>% rep(2),
    sfpv("65432;00200;1.")
)

vox <- c(
    "r1.",
    "r4 a4. g# e f#~8 f#1.",
    "r4 f#4. e c# b,~8 b,1.",
    "r4 a4. g# b f#~8 f#1.",
    "r4 a4. g# e f#~8 f#1.",
    "r4 f#4. e c# b,~8 b,1.",
    "r1.*5",
    "r4 a4. g# e f#~8 f#1.",
    "r4 f#4. e c# d~8 d1.",
    "r4 d4. e d c#~8 c#1.",
    "r4 c#4. d c# c#~8 c#1.",
    "r4 a4. g# e f#~8 f#1.",
    "r4 f#4. e c# b,~8 b,1.",
    "r4 a4. g# e f#~8 f#1.",
    "r4 f#4. e c# b,~8 b,1.",
    "r4 a4. g# e f#~8 f#1.",
    "r4 f#4. e c# b,~8 b,1.",
    "r1.*5"
) 
vox <- vox %>% paste(collapse=" ") %>% p

t_guit <- track(guit,voice=1,tuning=globalTuning)
t_vox  <- track(vox)
tracks <- trackbind(t_guit, t_vox)
tracks %>% score %>% tab('out.pdf',time="12/8", key="a")

How to play the guitar in R?

Hello leonawicz,

So cool package you have done. One question about tabr, could we play the guitar tablature in tabr after producing it ?

Thanks a lot~~

Shisheng

Transpose music staff with respect to corresponding tab staff

Most of the time that a standard music staff (e.g., 8va treble clef) is included above a tab staff, the two match, but sometimes this is not desired.

When using a capo, it is standard to transcribe the tab staff with respect to the capo'd fret as the new end of the guitar neck. This means that notes and chords are annotated on the tab staff the same as they would be if the capo was not used to transpose the music. The chord names and shapes are the same. For example, a song in Em will be written out on the tab staff as though it is still in Em, while it actually being in Fm is merely inferred from stating that the capo is on the first fret. It is not actually written out in Fm.

Normally, a standard music staff included above the tab staff will follow suit, also being written in Em though it may actually be in Fm due to capo 1 for instance. This is often of no consequence. However, sometimes you need to share the sheet music with other musicians playing other instruments and it is not helpful to tell them it's transposed from what they literally would play. In this case, it is best to have only the tab staff use an implied transposition while the other staff should be explicit.

The feature to add is (likely) an argument to track that allows for specifying transposition by a number of semitones to be applied to only the standard music staff relative to the tab staff. There should be a new data frame column in track table objects to store this, defaulting to 0. This information will be passed along through functions like trackbind and score and eventually passed to lilypond (or the tab wrapper) where the actual transposition of the specific staff will be applied in the generated LilyPond markup.

Using fretboard_plot on guitarChords

I'd like to plot some Chords with fretboard_plot() based on the dataset guitarChords. I prefer to use fretboard_plot() over tab() since the latter only outputs to pdf, and I'd like to display the chords in a markdown file as simply as possible.

Anyway, is it true that the dataset guitarChords is not really optimized for fretboard_plot() or vice versa? What I'm currently doing is something like this:

suppressMessages(library(tabr))
suppressMessages(library(dplyr))
suppressMessages(library(purrr))
suppressMessages(library(stringr))
suppressMessages(library(ggplot2))


guitarChords <- guitarChords %>%
  mutate(
    fretboard2 = map(fretboard,~as.vector(str_split_fixed(.x,";",7))[1:6]),
    mute = map(fretboard2,~.x == "x"),
    fretboard2 = map(fretboard2,~as.integer(ifelse(.x=="x" | .x == "o",0,.x))),
    name = paste0(toupper(root),ifelse(id == "M","",id))
  )
id <- 500
fretboard_plot(6:1,guitarChords$fretboard2[[id]],mute = guitarChords$mute[[id]]) +
  ggtitle(guitarChords$name[id])

Created on 2019-11-04 by the reprex package (v0.3.0)

Is there a better way?

using a list in trackbind()

For technical reasons, I have all my tracks in a list (x_list). Is there a way to use trackbind() on a list of tracks? I'd be happy to use purrr or the like, I just haven't found a way to implement this yet.

library(tabr)
x <- phrase("c ec'g' ec'g'", "4 4 2", "5 432 432")
x1 <- track(x)
x2 <- track(x, voice = 2)
x_list <- list(x1,x2)
trackbind(x_list)
#> Error: All arguments must be `track` tables.

Created on 2020-01-25 by the reprex package (v0.3.0)

Add predefined string tunings

While any arbitrary custom tuning can already be expressed explicitly, e.g., (e, a, d g b e'), add convenient IDs for common tunings. For example, dropD, DADGAD, etc.

Suppress tab staff

Hello,
Is there any way for a track to display a music staff without a tab staff? This would be useful for example on vocal tracks.

Thanks for creating tabr. I'm really enjoying it so far.

MIDI input to tabr output

Read midi file (see tuneR package), create mapping function to tabr phrase syntax for sending to LilyPond.

Aside/low hanging fruit: Does LilyPond already have its own feature for converting midi straight to sheet music?

If so, that's a worthwhile wrapper function in tabr. Even if so, it's still worth having the tabr mapping function in order to offer full control over the intermediary content where the midi data has been converted to the tabr R syntax and can be available for interactive user edits.

`lyrics` class object

Just a heads up note to say that I am considering adding a simple lyrics class that corresponds to noteworthy and noteinfo in structure and behavior. It would serve the purpose of mapping content from an arbitrary character vector by timestep. The goal would be to potentially pass this object to lilypond or related render_* wrapper function so that arbitrary lyrics text could be attached at specific timesteps as part of the lyrics line in LilyPond output.

This would also remain distinct from notate, which attaches different text to individual timesteps and is done so as part of noteinfo.

sh: : command not found

Hi,

When I run the example code:

require(tabr)
p1 <- p("r a2 c f d a f", "4 8*6")
track1 <- track(p1)
song <- score(track1)
tab(song, "phrase.pdf", key = "dm", time = "4/4", tempo = "4 = 120")

I receive the following error:

Engraving score to phrase.pdf

sh: : command not found
Warning message:
In system(call_string) : error in running command

Does the following mean it is not detecting the lilypond installation and would that account for the issue??

tabr_options()
$dev
[1] "pdf"

$midi
[1] TRUE

$lilypond
[1] ""

$midi2ly
[1] ""

$python
[1] "/usr/bin/python"

Relative music strings

Hello,

Thanks for implementing string numbers in music strings. I have another request.

Could you add an attribute for lilypond's \relative note entry mode?

I feel this would make transcription easier.

Thanks

can't use phrase helpers with phrases beginning in rests

> phr <- p('r c*3','4')
> notify(phr)
Error in FUN(X[[i]], ...) : subscript out of bounds
> phrase_notes(phr)
Error in FUN(X[[i]], ...) : subscript out of bounds
> phrase_info(phr)
Error in FUN(X[[i]], ...) : subscript out of bounds
> phrase_strings(phr)
Error in FUN(X[[i]], ...) : subscript out of bounds

only seems to happen when notes begins with a rest

Can plot_fretboard print "dot" fret numbers? (3, 5, 7, etc)

Hello,

Thanks for putting together this package, I'm using it to print practice scales for guitar, specifically the "plot_fretboard" function. I wanted to ask if there's a way to get this function to flag frets # 3, 5, 7, 9, 12, as would be on a regular guitar, for ease of use. Presently the function only prints the lowest fret number when it is non-zero.

I unsuccesfully tried to make a copy of the function to see if I could debug and pin-point the lines where fret numbers are printed. There are several function dependencies that seem to get broken using that approach.

Thanks,
Alex

macOS: unable to open

This package sounds amazing, which is why it's unfortunate that I hit a breaking bug.

When running this example my instance of Rstudio seems to have issues with rendering. Apon rending the cell in the RMD or after knitting I keep getting this error.

image

Any idea what might be prompting this?

Repeated sections in sheet music do not repeat in the midi file

Can tabr call lilypond to create a midi file of the tab yet?

If no, any interest in building out that feature? I am willing to help.

I understand if you feel it is outside the scope of the project but it may be useful for verifying you have the correct notation.

Mapping from string/fret to pitch

Add a handicap mapping function to allow string/fret combo input for users who prefer this convenience and are not thinking about pitch.

Context

LilyPond and tabr deal in pitch. LilyPond accepts string numbers as supplemental information. Between the two, this removes the freedom to vary and locks in fret number (given your stringing and tuning). This is encouraged because it's valuable to know what you are actually playing; it's great for learning the notes on the neck of the guitar whereas just thinking about string and fret number inhibits this kind of growth and understanding.

But just as important, the standard approach is also powerful in that it allows LilyPond to engrave correct notes no matter what stringing or tuning you tell it you are using. Pitch is pitch.

Nevertheless, this is a valuable add-on and can provide convenience, even if it should not serve as the fundamental basis for other package functionality.

To do

Write a function that takes string and fret numbers. This function must also know the stringing and tuning via additional argument, tuning (number of strings can be derived from tuning value). Six string standard tuning will be the default. Main arguments will be string and fret and should work just like the string arguments passed to phrase. This could be called sf_phrase with an alias sfp.

Since sf_phrase return pitch, but also is passed explicit string numbers as input to begin with, both of these pieces of information must be returned and passed to phrase for efficiency. sf_phrase can wrap around phrase.

Example: sfp(string, fret, info, tuning, ...) where (1) string and info pass through directly to phrase and (2) string and fret are mapped to notes.

Necessary differences from phrase: string numbers obviously no longer optional. Fret number can go much higher than string number and this gets annoying for double digits, e.g., fret 10 is not fret 1 followed by open string. Consider the best approach to minimize typing while keeping within the syntax and framework of tabr. This means requiring space-delimited time. Therefore, simultaneous notes must be adjacent (no spaces). In tabr this is done with (). Typing (12)(14)(14)(12)(12)(12) for a chord would be very annoying, but it can be simplified with helper functions. See hp helper for examples such as a long sequence of 16th note hammer/pulls.

Tuning render error

When I use a tuning with a lot of sharps, the tuning on my score pdf gets messed up. It looks like a horizontal list of F CIS A FIS B...

tuning <- "f#,b,f#ac#'f#'"
track(p('c4 d e'),tuning=tuning)  %>% score %>% lilypond('test.ly', key="a")

The compiled lilypond looks like this - some issues with quotes, I guess:

\set TabStaff.instrumentName = \markup  { \hspace #7 \override #'(baseline-skip . 1.5)  \column \fontsize #-4.5 \sans { F#" CIS A FIS B "FIS } }

Per-staff key signature

Hello,

Is there any support for different key signatures on each staff? I see that tracks can be transposed, but then their key signature seems limited to their transposition from the global key.

I could hack this to get what I want, but I'd rather not write phrases in arbitrary reverse transpositions.

Could ms_key be changed to support any key signature? Beyond compliance with shared staves, it seems doable to me.

plot 5 string instrument

hi thanks for your code it is great!

I would like to create chord diagrams for a guitar that has 5 strings. I have not been able to modify your example with success
I have only removed the 6th string on the chord diagram.
How can I tell to plot_chord() that this is a 5 string instrument?

chords <- c("02210",
"32010",
"00232",
"33211",
"22100")
id <- c("Am", "C", "D", "F", "E")

g <- map2(chords, id, ~{
plot_chord(.x, "notes", point_size = 8, fret_range = c(0, 4), accidentals = "sharp", asp = 1.25) +
ggtitle(.y)
})

grid.arrange(grobs = g, nrow = 2)

Error when specifying E-flat in octave 4 or 5

This works as expected:

phrase("e_", info = "4")

But either of these results in an error:

phrase("e_4", info = "4")
phrase("e_'", info = "4")

The message is Error in mapply(substr, x, idx, idx + c(diff(idx), nchar(x) - utils::tail(idx, : zero-length inputs cannot be mixed with those of non-zero length

Flattening any other note in octave 4 is fine.

Tested under:
tabr 0.1.0
R 3.4.4
Windows 7

Invalid `key` with ms_transpose

> p('a','1') %>% track(ms_transpose=1) %>% score() %>% tab('test.pdf')
#### Engraving score to test.pdf ####
Error: Invalid `key`. See `keys()`.

dotted rests are not phrasey

p("r", "8.") %>% track %>% score %>% tab("test.pdf")
Error: x is not phrasey

This only seems to happen when the string has nothing but rests.

tab is not working on MacOS

The 'system()' line in tab works for Windows OS only.

For MacOS needs to be something like:
system(paste0(tabr_options()$lilypond, " --", fp$ext, " -dstrip-output-dir=#f ", fp$lp))

Also, the options would be something like
tabr_options(lilypond = "/Users/userx/bin/lilypond")

without the ".exe" at the end.

Great package BTW. Thanks for doing this. Looking forward to explore it more.

Christos

R code in README won't work

The tabr "Hello World" example in README greets you with an error:

> p("r a2 c f d a f", "4 8*6", "x 5 5 4 4 3 4") %>% track %>% score %>%
  tab(song, "phrase.pdf", key = "dm", time = "4/4", tempo = "4 = 120")

Error in gsub("\\\\", "/", file) : object 'song' not found

The first parameter to tab (song) is not needed, since you used %>% passing it.

tab() with trackbind only returns two phrases

If multiple tracks are bound together with trackbind() and then passed to tab() (via score()), only the first two phrases are printed in the sheet. Is 2 the maximum number of phrases?

library(tabr)
e1 <- p("e2","1*1", 6) %>%
  track(voice = 1)
e2 <- p("e3","1*1", 3)%>%
  track(voice = 2)
e3 <- p("e4","1*1", 1)%>%
  track(voice = 3)


trackbind(e1,e2,e3,id = rep(1,3))%>%
  score() %>%
  tab("test.pdf", midi = FALSE)

returns this:

grafik

Specifying strings in music strings

I'm learning to use music strings for transcription.
But I haven't yet found a good way specify string numbers when starting from a music string.
I end up using something like p(music_notes(x),music_info(x),strings).
Could you suggest a simpler way?

What would be really nice is if timesteps in music strings could have a delimiter for string number.

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.