R/Chords.R

Defines functions sonority makeChordTransformer tertianSet.token tertianSet.factor tertianSet.NULL tertianSet.logical tertianSet.tertianSet tertianSet integer2tset harte2tset chord2tset figuredBass2tset tertian2tset sciQualities2tset harm2tset roman2tset parseFiguration figurationFill completeExtensions collapseEnharmonicSets findBestInversion steps2thirds extensions2qualities triad2sciQuality extension2bit integer2tset tset2harte tset2chord tset2tertian tset2harm tset2roman tset2figuredBass tset2tonalHarmony reduceFigures tset2triadLabel tset2extensions tset2alterations is.minor.default is.major.default is.tertianSet rootPosition getInversion getExtensions getBassTint getBass tset

Documented in is.major.default is.minor.default is.tertianSet sonority tertianSet tertianSet.factor tertianSet.logical tertianSet.NULL tertianSet.tertianSet tertianSet.token tset

################################## ###
# tertianSet S4 class ################
################################## ###

## tertianSetS4 documentation ----

#' Tertian set
#' 
#' `tertianSet` is one of [humdrumR's][humdrumR] 
#' types of tonal data, representing Western tertian harmonies.
#' `tertianSet` is a subclass of `diatonicSet` (and thence, `struct`).
#' 
#' The only structural addition, compared to `diatonicSet`, is the `Extensions` slot.
#' This slot indicates which tertian chord members are active in the chord.
#' There are seven possible chord members: 
#' the root, third, fifth, seventh, ninth, eleventh, and thirteenth.
#' Every possible combination of these seven degrees is represented by a single integer, corresponding
#' to the 7-bit representation of on/offs on the seven degrees in reverse order (13, 11, 9, 7, 5, 3, 1).
#' For example, the integer `15` corresponds to a seventh chord: in binary, 15 is `0001111`.
#' The initial three zeros indicate that the 13th, 11th, and 9th are *not* part of the harmony, while the four ones
#' indicate that the root, third, fifth, and seventh *are* part of the harmony.
#' Ultimately, adding or removing a chord degree from a harmony can be achieved by adding the power of
#' two associated with that degree: 
#' 
#' + **Root**: \eqn{\pm 1}
#' + **Third**: \eqn{\pm 2}
#' + **Fifth**: \eqn{\pm 4}
#' + **Seventh**: \eqn{\pm 8}
#' + **Ninth**: \eqn{\pm 16}
#' + **Eleventh**: \eqn{\pm 32}
#' + **Thirteenth**: \eqn{\pm 64}
#' 
#' `tertianSet` has many specific methods defined for reading/writing harmonic information.
#' 
#' @seealso The main way to create `tertianSet` S4 objects is with the [tertianSet()] pitch parser.
#' @family {Tonal S4 classes}
#' @name tertianSetS4
NULL

## Definition, validity, initialization ####

#' @rdname tertianSetS4
#' @export 
setClass('tertianSet', 
         contains = 'diatonicSet',
         slots = c(Extensions = 'integer',
                   Inversion = 'integer'))

setValidity('tertianSet', 
            function(object) {
                all(object@Extensions >= 0L &
                      object@Extensions <= 2^7 &
                      object@Inversion >= 0 &
                      object@Inversion < 7, na.rm = TRUE)
            })

## Constructors ####

#' @rdname tertianSetS4
#' @export
tset <- function(root = 0L, signature = 0L, alterations = 0L, cardinality = 3L, extension = NULL, inversion = 0L) {
    if (is.tonalInterval(root)) root <- root@Fifth
    
    if (length(root) == 0L && length(signature) == 0L) {
      return(new('tertianSet', 
                   Root = integer(), 
                   Signature = integer(), 
                   Alteration = integer(), 
                   Extensions = integer(),
                   Inversion = integer()))
    }
    match_size(root = root, signature = signature, alterations = alterations,
               cardinality = cardinality, extension = extension, inversion = inversion, 
               toEnv = TRUE)
    
    if (is.null(extension)) {
      # root <- .ifelse(cardinality == 0L, NA_integer_, root)
      extension <- c(0L, 1L, 3L, 7L, 15L, 31L, 63L, 127L)[cardinality + 1L]
    } else {
      root[extension == 0L] <- NA_integer_
    }
   
    
    new('tertianSet', 
        Root = as.integer(root), 
        Signature = as.integer(signature), 
        Alteration = as.integer(alterations), 
        Extensions = extension,
        Inversion = as.integer(inversion))
}

## Accessors ####


getBass <- function(tset){
    LO5th(tset)[ , 1L]
}

getBassTint <- function(tset){
    tint( , getBass(tset)) 
} 

getExtensions <- function(tset) {
    
   rootpos <- tset@Extensions
   inverted <- bitwRotateR(rootpos,  getInversion(tset), nbits = 7L)

   inverted <- inverted + ((inverted - 1) %% 2) # make sure root position is there always 

   bits <- ints2bits(inverted, nbits = 7L) == 1L
   colnames(bits) <- c('Root', nthfix(c(3, 5, 7, 9, 11, 13)))
   rownames(bits) <- NULL
   bits
   
}


getInversion <- function(tset, inversion.labels = NULL) {
  
  inversion <- tset@Inversion
  if (!is.null(inversion.labels)) inversion <- inversion.labels[1L + (inversion %% length(inversion.labels))]
  inversion
}

#' @export
rootPosition <- function(tset) {
  is.tertianSet(tset)
  tset@Inversion <- rep(0L, length(tset))
  tset
}



## Logic methods ####

### is.methods #####

#' @rdname tertianSetS4
#' @export
is.tertianSet <- function(x) inherits(x, 'tertianSet')


#### Tonal is.methods ####

#' @rdname is.major
#' @export
is.major.default <- function(x, ...) {
   parsed <- tertianSet(x, ...)
   if (any(is.na(parsed))) {
     keys <- diatonicSet(x, ...)
     if (all(!is.na(keys)[!is.na(parsed)]) && any(!is.na(keys)[is.na(parsed)])) parsed <- keys
   }
  
   is.major.diatonicSet(parsed) 
   
}
#' @rdname is.major
#' @export
is.minor.default <- function(x, ...) {
  parsed <- tertianSet(x, ...)
  if (any(is.na(parsed))) {
    keys <- diatonicSet(x, ...)
    if (all(!is.na(keys)[!is.na(parsed)]) && any(!is.na(keys)[is.na(parsed)])) parsed <- keys
  }
  
  is.minor.diatonicSet(parsed) 
  
}

## Order/relations methods ####

#' @rdname diatonicSetS4
#' @export
setMethod('==', signature = c('tertianSet', 'tertianSet'),
          function(e1, e2) {
              checkSame(e1, e2, "==")
              f1 <- LO5th(e1)
              f2 <- LO5th(e2)
              
              same <- f1 == f2 | (is.na(f1) & is.na(f2))
              
              rowSums(same, na.rm = TRUE) == 7L
          })

## Arithmetic methods ####

### Addition/Subtraction ####

#' @export
setMethod('+', signature = c('tertianSet', 'tonalInterval'),
          function(e1, e2) {
            match_size(e1 = e1, e2 = e2, toEnv = TRUE)
            
            lof <- e2@Fifth
            
            tset(getRoot(e1) + lof, getSignature(e1) + lof,
                 inversion = e1@Inversion, 
                 extension = e1@Extensions,  alterations = e1@Alteration)
            
          })


###################################################################### ###
# Deparsing Chord Representations (tset2x) ###############################
###################################################################### ###


## Deparsing (tertianSet) documentation ----

#' Generating ("deparsing") chord representations
#' 
#' [humdrumR] includes a easy-to-use system for 
#' generating a variety of tertian harmony (chord) representations,
#' which can be flexibly modified by users.
#' "Under the hood" `humdrumR` represents all tonal chord information using the [same underlying representation][tertianSetS4],
#' which is typically extracted from input data using the [chord parser][chordParsing].
#' This representation can then be "deparsed" into a variety of predefined output formats (like `**harm`), 
#' or into new formats that you create!
#' 
#' Deparsing is the second step in the [chord function][chordFunctions] processing pipeline:
#' 
#' + **Input** representation `|>` 
#'   + *Parsing* `|>`
#'     + **Intermediate** ([tertianSet][tertianSetS4]) representation `|>`
#'     + **Transformation**  `|>`
#'   + *Deparsing* (DEPARSING ARGS GO HERE) `|>`
#' +  **Output** representation 
#' 
#' Various pitch representations can be generated using predefined [chord functions][chordFunctions] like [chord()]
#' [tertian()], and [roman()].
#' All of these functions use a common deparsing framework, and are specified using different combinations of arguments
#' to the deparser.
#' By modifying these *"deparsing" arguments*, you can exercise 
#' fine control over how you want pitch information to be represented in your output.
#' 
#' @seealso All `humdrumR` [chord functions][chordFunctions] make use of the deparsing functionality.
#' @name chordDeparsing
NULL


## Chord deparsers ####




### Chord representations ####



tset2alterations <- function(x, Key = dset(0,0), 
                             qualities = FALSE, 
                             inversion = TRUE, 
                             absoluteSpecies = TRUE,  implicitSpecies = FALSE, 
                             dominantSpecies = FALSE,
                             explicitNaturals = FALSE, ...) {
  # this produces either accidentals or qualities, depending on the parts argument
  
  if (!inversion) x <- rootPosition(x)
  bass <- getBass(x)
  
  if (absoluteSpecies) {
    roots <- getRoot(x)
    x <- x - getRoot(x)
    Key <- if (dominantSpecies) dset(0, -1L) else dset(0L, getMode(Key) - roots)
  }
  if (explicitNaturals) {
    x <- x - getRoot(Key)
    Key <- Key - getRoot(Key)
  }
  
  LO5ths <- LO5th(x)
  tints <- tint( , c(LO5ths))
  figures <- tint2tonalChroma(tints,  Key = Key, qualities = qualities, complex = FALSE, 
                              parts = 'species',
                              implicitSpecies = implicitSpecies, explicitNaturals = explicitNaturals, ...)
  
  # colnames(figures) <- extensions
  # rownames(figures) <- tint2simplepitch(tint( , bass), Key = dset(0, 0), quality.cautionary = TRUE)
  figures %<-matchdim% LO5ths
  
}


tset2extensions <- function(x, extension.simple = FALSE, inversion = TRUE, inverted, ...) {
  extensions <- c(1L, 3L, 5L, 7L, 9L, 11L, 13L)
  
  if (!inversion) x <- rootPosition(x)
  
  extensions <- matrix(extensions, byrow = TRUE, ncol = 7L, nrow = length(x))
  colnames(extensions) <- c('Root', nthfix(c(3, 5, 7, 9, 11, 13)))
  
  if (extension.simple) extensions[inverted, ] <- genericStep(extensions[inverted, ])
  extensions[!getExtensions(x)] <- NA_integer_
  
  extensions
}




tset2triadLabel <- function(x, root, root.case = TRUE, 
                            major = 'M', minor = 'm', diminish = 'o', augment = '+', ...) {
  
  perfect <- 'P'
  
  qualities <- tset2alterations(x, qualities = TRUE, inversion = FALSE,  step = FALSE,
                                explicitNaturals = TRUE, implicitSpecies = FALSE,
                                major = major, minor = minor, diminish = diminish, augment = augment, perfect = perfect)
  
  qualities <- qualities[ , c('3rd', '5th'), drop = FALSE]
  thirds <- qualities[ , '3rd', drop = FALSE]
  fifths <- qualities[ , '5th', drop = FALSE]
  thirds[is.na(thirds)] <- '.'
  fifths[is.na(fifths)] <- '.'
  
  #
  triadQuality <- rep('?', nrow(qualities))
  

  
  ## prepare labels for known combinations of third and fifth qualities
  reductions <- matrix('?', ncol = 4, nrow = 5, dimnames = list(c(diminish, minor, major, augment, '.'), c(diminish, perfect, augment, '.')))
  # reductions[] <- paste0('(', outer(rownames(reductions), colnames(reductions), paste0), ')')
  
  reductions <- local({
                       reductions[minor, diminish] <- diminish
                       reductions[minor, perfect] <- minor
                       reductions[major, perfect] <- major
                       reductions[major, augment] <- augment
                       reductions[major, '.'] <- paste0('3', major)
                       reductions[minor, '.'] <- paste0('3', minor)
                       reductions['.', perfect] <- paste0('5', major)
                       reductions['.', augment] <- paste0('5', augment)
                       reductions['.', diminish] <- paste0('5',diminish) 
                       reductions['.', '.'] <- paste0('1', major) 
                       reductions
                     }
  )
  
  ## get labels
  known <- thirds %in% rownames(reductions) & fifths %in% colnames(reductions)
  
  triadQuality[known] <- reductions[cbind(thirds[known], fifths[known])]
  
  
  if (!is.null(root) && root.case)  {
    root[substr(thirds, 0, 1) %in% c(minor, diminish)] <- tolower(root[substr(thirds, 0, 1) %in% c(minor, diminish)])
    triadQuality[triadQuality %in% c(major, minor)] <- ""
  }
  
  
  list(triadQuality = triadQuality, root = root)
  
  
}


reduceFigures <- function(alterations, extensions, 
                          triadQuality, root.case = FALSE,
                          inversion, step = TRUE,
                          extension.shorthand = TRUE, extension.simple = TRUE,
                          extension.add = TRUE, extension.sus = TRUE, 
                          extension.decreasing = TRUE, 
                          extension.sep = '', flat = '-', minor = 'm', diminish = 'o', ...) {
  if (is.null(extensions)) extensions <- array("", dim = dim(alterations))
  if (is.null(alterations)) alterations <- array("", dim = dim(extensions))
  
  inverted <- inversion > 0L
  
  present <- !is.na(alterations) 
  tags <- array(NA_character_, dim = dim(alterations))
  

  roots  <- sweep(col(extensions), 1, ((1L - inversion - 1L) %% 7L) + 1L, '==')
  thirds <- sweep(col(extensions), 1, ((2L - inversion - 1L) %% 7L) + 1L, '==')
  fifths <- sweep(col(extensions), 1, ((3L - inversion - 1L) %% 7L) + 1L, '==')
  
  # get rid of alterations that are already taken care of by the quality!
  if (!is.null(triadQuality)) {
   
    
    if (root.case) {
      alterations[thirds & alterations %in% c(flat, minor)] <- '' 
      alterations[fifths & alterations  ==     diminish   ] <- ''
    }
    alterations[!is.na(alterations) & 
                  (roots | thirds | fifths) & 
                  ((row(alterations) %in% which(triadQuality != '?')) | (alterations == 'n'))] <- ""
    
  }

  
  
  if (any(!inverted) && extension.sus) {
    
    nines.elevens <- col(extensions) %in% 5:6 & present
    sus <- sweep(nines.elevens, 1, !present[ , '3rd'] & !inverted, `&`) & alterations == ''
    extensions[sus] <- ((extensions[sus] - 1L) %% 7L) + 1L
    tags[sus] <- 'sus'
  }
  
  if (any(!inverted) && extension.add) {
    
    nines.11s.13s <- col(extensions) %in% 5:7 & present
    adds <- sweep(nines.11s.13s, 1, !present[ , '7th'] & !inverted, `&`) 
    
    if (extension.sus) adds <- adds & !sus
    extensions[adds] <- ((extensions[adds] - 1L) %% 7L) + 1L
    tags[adds] <- 'add'
  }
  
  # missing triad tones
  anysus <- rowSums(tags == 'sus', na.rm = TRUE)
  powerchords <- rowSums(extensions == 1L | extensions == 5L, na.rm = TRUE)
  
  tags[!row(extensions) %in% which(inverted | anysus > 0L | powerchords == 2L) & 
         is.na(extensions) & 
         col(tags) %in% 2L:3L 
       & is.na(tags)] <- 'no'
  
  extensions[which(tags == 'no')] <- ((col(extensions)[which(tags == 'no')] - 1L) * 2L) + 1L
  #
  if (extension.shorthand) {
    # if (extension.simple && any(inverted)) {
      # extensions[inverted, ] <- genericstep(extensions[inverted, ])
    # }
    chorddegree <- sweep(extensions, 1, 2L * inversion, '+')
    chorddegree[chorddegree %in% c(8L, 10L, 12L)] <- chorddegree[chorddegree %in% c(8L, 10L, 12L)] - 7L
    chorddegree[which(chorddegree > 13L, arr.ind = TRUE)] <- chorddegree[which(chorddegree > 13L, arr.ind = TRUE)] - 14L
    
    hide <- sweep(col(chorddegree), 1, apply(chorddegree, 1, \(row) max(4L, which.max(row))), '<') 
    if (any(inverted)) hide <- hide & !sweep(chorddegree > 5, 1, inverted, '&')
    
    
    extensions[(hide & alterations == "" & is.na(tags)) | extensions == 1L] <- NA_integer_
    
  }
  
 
  # order
  
  alterations[] <- .paste(tags, alterations, if (step) extensions, fill = ".", na.if = all)
  
  figures <- if (!is.null(extension.decreasing)) {
    extensions[grepl('sus|add', alterations)] <- extensions[grepl('sus|add', alterations)] + 20 # put sus last
    order <- order(row(extensions), extensions, decreasing = extension.decreasing)
    # order <- order[!is.na(extensions[order])]
    
    tapply(alterations[order], row(extensions)[order], c, simplify = FALSE)
  } else {
    lapply(1L:nrow(alterations), \(i) alterations[i, ])
  }
  
  output <- sapply(figures, \(f) paste(.paste(extension.sep[1], f, extension.sep[2], na.if = all, sep = ''), collapse = ''))
  
  gsub('\\.+$', '', output)
  
  #
  
}


tset2tonalHarmony <- function(x,
                              parts = c('root', 'quality', 'figuration'), 
                              root = TRUE, quality = TRUE, figuration = TRUE, inversion = TRUE, bass = FALSE, 
                              figurationArgs = list(),
                              root_func = tint2romanRoot, bass_func = root_func, bass.sep = '/',
                              root.case = TRUE,
                              Key = NULL, keyed = FALSE,
                              inversion.labels = NULL,
                              collapse = TRUE, sep = '', ...) {
  Key <- diatonicSet(Key)
  
  if (keyed && !is.null(Key)) {
    Key <- rep(Key, length.out = length(x))
    x[!is.na(Key)] <- x[!is.na(Key)] + getRoot(Key[!is.na(Key)])
  }
  Key <- CKey(Key)
  
  parts <- matched(parts, c('root', 'quality', 'figuration', 'inversion', 'bass'))
  
  
  bass      <- if (bass) ifelse(!root | (getInversion(x) > 0), 
                                paste0(bass.sep, bass_func(getBassTint(x) - tint(1, 0), Key = Key, ...)), 
                                "")
  root      <- if (root) root_func(getRootTint(x), Key = Key, ...) 
  
  quality   <- if (quality) {
    c("quality", "root") %<-% tset2triadLabel(x, root, root.case, ...)
    quality
  }
 
  
  figuration <- if (figuration) {
    extensions  <- do.call('tset2extensions', c(list(x, inversion = inversion, inverted = getInversion(x) > 0L), figurationArgs))
    alterations <- do.call('tset2alterations', c(list(x, Key = Key, inversion = inversion, step = FALSE), figurationArgs[names(figurationArgs) != 'step']))
    
    figuration <- do.call('reduceFigures', c(list(alterations, extensions, ...,
                                                  quality, root.case, if (inversion) getInversion(x) else 0L), figurationArgs))
    quality[quality == '?'] <- ""
    quality[grepl('sus|add', figuration) & quality %in% c('5MAJOR', '5MINOR')] <- ''
    figuration
    
  }
  
  
  
  inversion.label <- if (!is.null(inversion.labels)) getInversion(x, inversion.labels = inversion.labels)
  
  if (collapse) {
      pasteordered(parts, root = root, quality = quality, figuration = figuration, inversion = inversion.label, bass = bass, sep = sep)
  } else {
      list(root = root, quality = quality, figuration = figuration, inversion = inversion.label, bass = bass)[parts]
  }
  
}



tset2figuredBass <- function(x, figurationArgs = list(),  ...) {
  figArgs <- list(implicitSpecies = TRUE, flat = 'b', qualities = FALSE,
                  absoluteSpecies = FALSE, extension.decreasing = TRUE,
                  extension.simple = TRUE)

  figArgs[names(figurationArgs)] <- figurationArgs
  
  t2tH <- partialApply(tset2tonalHarmony, keyed = TRUE,
                       parts = c('bass','figuration'),
                       root.case = FALSE,
                       root = FALSE, bass = TRUE, bass_func = tint2kern,
                       figuration = TRUE, quality = FALSE,
                       extension.shorthand = TRUE, #extension.simple = TRUE,
                       extension.sus = FALSE, extension.add = FALSE,
                       inversion = TRUE,
                       sep = ' ', bass.sep = '')
  figures <- t2tH(x, figurationArgs = figArgs, ...)
  
  
  # if (extension.shorthand) {
  #   figures <- stringr::str_replace(figures,'([^913])753|^753', '\\17')
  #   figures <- stringr::str_replace(figures, '([^9713])63|^63', '\\16')
  #   figures <- stringr::str_replace(figures, '([^9713])653|^653', '\\165')
  #   figures <- stringr::str_replace(figures, '([^9713])643|^643', '\\143')
  #   figures <- stringr::str_replace(figures, '([^9713])642|^642', '\\142')
  # }
  # 
  figures
  
  
}


tset2roman <- function(x,  Key = dset(0, 0), figurationArgs = c(), ...) {
  
  figArgs <- list(implicitSpecies = TRUE, flat = 'b', qualities = FALSE, 
                  extension.shorthand = TRUE, extension.simple = TRUE, absoluteSpecies = FALSE,
                  extension.sus = TRUE, extension.add = TRUE)
  figArgs[names(figurationArgs)] <- figurationArgs
  
  t2tH <- partialApply(tset2tonalHarmony, 
                       parts = c('root', 'quality', 'figuration', 'inversion'), 
                       root_func = tint2romanRoot, 
                       implicitSpecies = TRUE,
                       rootCase = TRUE,
                       inversion.labels = NULL,
                       inversion = TRUE)
  
  t2tH(x, figurationArgs = figArgs, Key = Key, ...)
  
}


tset2harm <- function(x,  Key = dset(0, 0), figurationArgs = c(), ...) {
  figArgs <- list(implicitSpecies = TRUE, qualities = TRUE, absoluteSpecies = TRUE,
                  diminish = 'D', augment = 'A', 
                  extension.shorthand = TRUE, extension.simple = FALSE, extension.decreasing = FALSE,
                  extension.sus = TRUE, extension.add = TRUE)
  figArgs[names(figurationArgs)] <- figurationArgs
  
  t2tH <- partialApply(tset2tonalHarmony, 
                       parts = c('root', 'quality', 'figuration', 'inversion'), 
                       root_func = tint2romanRoot, 
                       implicitSpecies = TRUE,
                       rootCase = TRUE,
                       natural = '#',
                       inversion.labels = c('', 'b', 'c', 'd', 'e', 'f', 'g'),
                       inversion = FALSE)
  
  Key@Alteration[getMode(Key) == -3L & Key@Alteration == 0L] <- 1L
  
  t2tH(x, figurationArgs = figArgs, Key = Key, ...)
  
}

tset2tertian <- function(x,  figurationArgs = c(), ...) {
  figArgs <- list(implicitSpecies = FALSE, explicitNaturals = TRUE, diminish = 'o', augment = '+',
                  absoluteSpecies = TRUE, qualities = TRUE, step = FALSE)
  
  figArgs[names(figurationArgs)] <- figurationArgs
  
  
  t2tH <- partialApply(tset2tonalHarmony, keyed = TRUE,
                       parts = c('root', 'quality', 'figuration', 'inversion'), 
                       root_func = tint2simplepitch, 
                       root.case = FALSE,
                       root = TRUE, quality = TRUE, figuration = TRUE, 
                       inversion = FALSE, bass = TRUE,
                       implicitSpecies = FALSE, inversion.labels = c('', '/3', '/5', '/7', '/2', '/4', '/6'),
                       extension.shorthand = TRUE, extension.simple = FALSE,
                       extension.decreasing = NULL,
                       extension.add = FALSE, extension.sus = FALSE)
  t2tH(x, figurationArgs = figArgs, ...)
}


tset2chord <- function(x, figurationArgs = c(), major = NULL, ...) {
  figArgs <- list(absoluteSpecies = TRUE, implicitSpecies = TRUE, extension.decreasing = FALSE, dominantSpecies = TRUE,
                  flat = 'b', qualities = FALSE, natural = 'maj')
  figArgs[names(figurationArgs)] <- figurationArgs
  
  t2tH <- partialApply(tset2tonalHarmony, keyed = TRUE,
                       parts = c('root', 'quality', 'figuration', 'bass'), 
                       root_func = tint2simplepitch, 
                       minor = 'min', diminish = 'dim',
                       root = TRUE, quality = TRUE, figuration = TRUE, inversion = FALSE, bass = TRUE,
                       implicitSpecies = FALSE, root.case=FALSE,
                       extension.shorthand = TRUE, extension.simple = FALSE,
                       extension.add = TRUE, extension.sus = TRUE)
  
  chords <- t2tH(x, figurationArgs = figArgs, major = major %||% "MAJOR", ...)
  
  if (is.null(major)) chords <- stringr::str_replace(chords, "MAJOR", '')
  
  stringr::str_replace(chords, 'maj7([139]{1,2})', 'maj\\1')
  
}

tset2harte <- function(x, Key = NULL, figurationArgs = list(), flat = '-', ...) {
  
  Key <- diatonicSet(Key)
  
  if (!is.null(Key)) {
    Key <- rep(Key, length.out = length(x))
    x[!is.na(Key)] <- x[!is.na(Key)] + getRoot(Key[!is.na(Key)])
  }
  
  root <- getRootTint(x)
  
  qualities <- tset2alterations(x, flat = 'b', inversion = FALSE)
  extensions <- tset2extensions(x, inversion = FALSE, inverted = x@Inversion > 0)
  extensions <- array(.paste(qualities, extensions), dim = dim(qualities))
  
  fig <- apply(extensions, 1, .paste, collapse = ',')
  
  fig <- paste0('(', fig, ')')
  
  shorthand <- c('3,5' = 'maj', 'b3,5' = 'min', 'b3,b5' = 'dim', '3,#5' = 'aug',
                 '3,5,7' = 'maj7', 'b3,5,b7' = 'min7', '3,5,b7' = '7', 'b3,b5,b7' = 'hdim7', 'b3,b5,bb7' = 'dim7', 'b3,5,7' = 'minmaj7',
                 '3,5,6' = 'maj6', 'b3,5,6' = 'min6',
                 '3,5,7,9' = 'maj9', 'b3,5,b7,9' = 'min9', '3,5,b7,9' = '9',
                 '5,11' = 'sus4', '5,9' = 'sus2')
  names(shorthand) <- paste0('(1,', names(shorthand), ')')
  fig[fig %in% names(shorthand)] <- shorthand[fig[fig %in% names(shorthand)]]
  
  
  
  root <- tint2simplepitch(root, flat = flat, ...)
  
  # bass
  bass <- local({
    inverted <- x@Inversion > 0L
    bass <- character(length(x))
    bass[inverted] <- paste0('/', extensions[cbind(which(inverted), x@Inversion[inverted] + 1L)])
    bass
    
  })
  paste0(root, ':', fig, bass)
  
}



  
###################################################################### ###
# Parsing Chord Representations (x2tset) #################################
###################################################################### ###


## Parsing (tertianSet) documentation ----

#' Parsing chord information
#' 
#' [humdrumR] includes a easy-to-use but powerful system for *parsing* tertian harmony information:
#' various basic chord representations (including `numeric` and `character`-string representations) can be "parsed"---read
#' and interpreted by `humdrumR`.
#' For the most part, parsing automatically happens "behind the scenes" whenever you use any humdrumR [chord function][chordFunctions], like [harm()]
#' [roman()], or [chord()].
#' 
#' @seealso All `humdrumR` [chord functions][chordFunctions] make use of the deparsing functionality.
#' @name chordParsing
NULL

## Chord parsers ####


### Numeric

integer2tset <- function(int) tset(int, 0)

### Extensions/Figuration ####

extension2bit <- function(str) {
  
  extensions <- stringr::str_extract_all(str, captureRE(c('7', '9', '11', '13', 'sus4', 'add6', 'add2')))
  
  bit <- 7L # triad
  
  sapply(extensions,
         \(exten) {
           if (any(exten %in% c('9', '11', '13')) & !any(exten == '7')) bit <- bit + 8L
           
           if (any(stringr::str_detect(exten, 'sus'))) bit <- bit - 2L
           
           
           
           exten <- stringr::str_replace(exten, captureRE(c('65', '43', '42')), '7')
           exten <- stringr::str_replace(exten, 'add2', '9')
           exten <- stringr::str_replace(exten, 'add9', '9')
           exten <- stringr::str_replace(exten, 'sus4', '11')
           exten <- stringr::str_replace(exten, 'add6', '13')
           
           bit + sum(c(`7` = 8L, `9` = 16L, `11` = 32L, `13` = 64L)[exten])
         })
}




triad2sciQuality <- function(triad, extensionQualities, incomplete,
                             major = 'M', minor = 'm', perfect = 'P', diminish = 'o', augment = '+',
                             ...) {
  
  
  triadQualities <- local({
                           quals <- cbind(perfect, 
                                          c(major, minor, minor, major), 
                                          c(perfect, perfect, diminish, augment))
                           rownames(quals) <- c(major, minor, diminish, augment)
                           quals <- cbind(quals[triad, , drop = FALSE ], '.', '.', '.', '.')
                           
                           quals[incomplete == '1', 2L] <- '.'
                           quals[incomplete == '1', 3L] <- '.'
                           quals[incomplete == '3', 3L] <- '.'
                           quals[incomplete == '5', 2L] <- '.'
                           quals
                           
                         })
  
  extensionQualities[col(extensionQualities) <= 3L & extensionQualities == '.'] <- triadQualities[col(extensionQualities) <= 3L & extensionQualities == '.']
  extensionQualities[ , 2L:3L] <- triadQualities[ , 2L:3L]
  
  apply(extensionQualities, 1L, paste, collapse = '')
  
}



extensions2qualities <- function(root, figurations, triadalts, Key = NULL, qualities = FALSE, ...) {
  
  mode <- if(is.null(Key)) 0L else getMode(Key)
  
  dots <- rep('.', 7L)
  Map(function(r, deg, acc, m) {
    redundantroot <- deg == 1 & acc == ""
    deg <- deg[!redundantroot]
    acc <- acc[!redundantroot]
    if (length(deg) == 0L) return(dots)
    step <- step2tint(deg, step.labels = 1L:14L)
    
    alterations <- specifier2tint(acc, step, qualities = qualities,  
                                  Key = dset(0L, m - r), implicitSpecies = TRUE, ...)
    
    qualities <- tint2specifier(step + alterations, qualities = TRUE, ...)
    
    dots[1L + ((deg - 1L) %/% 2L)] <- qualities
    dots
  }, root, figurations$Degrees, figurations$Accidentals, rep(mode, length.out = length(root))) |> do.call(what = 'rbind')
  
  
  
}








steps2thirds <- function(steps, groupby) {
  steps <- 1L + ((steps - 1L) %% 14L)
  extension <- ifelse(steps %% 2L == 0L, steps + 7L, steps)
  
  rotations <- outer(extension, c(2L, 4L, 6L, 8L, 10L, 12L, 14L), '-') %% 14L

  # hasroot     <- do.call('cbind', by(rotations == 1L, groupby, colSums))
  # hasthird    <- do.call('cbind', by(rotations == 3L, groupby, colSums))
  # haseleventh <- do.call('cbind', by(rotations == 11L, groupby, colSums))
  # hasfifth    <- do.call('cbind', by(rotations == 5L, groupby, colSums))

  # sums <- do.call('cbind', by(rotations, groupby, colSums, simplify = FALSE))
  # sums[!hasthird & haseleventh] <- sums[!hasthird & haseleventh] - 7L

  #use order because it does hierarchical ordering
  # picks <- order(c(col(hasroot)),
                 # c(-hasroot), c(-hasfifth), c(-hasthird), c(sums))
  # picks <- (picks[seq_along(picks) %% 7L == 1L] - 1L) %% 7L + 1L
  
  #
  picks <- max.col(-tapply(rotations, list(rep(groupby, 7L), col(rotations)), max))
  
  #
  as.integer((rotations[cbind(seq_along(extension), picks[match(groupby, unique(groupby))])] - 1L) / 2L)
}




findBestInversion <- function(int) {
  empty <- int == 0L
  
  # takes a (bitwise) integer representation of extensions
  inversions <- outer(int[!empty], 0L:6L, bitwRotateL, nbits = 7L)
  
  inversions[inversions %% 2L == 0L] <- 256L
  
  bestPick <- integer(length(int))
  bestPick[!empty] <- max.col(-inversions)
  
  int[!empty] <- inversions[cbind(seq_len(nrow(inversions)), bestPick[!empty])]
  
  list(Extension = as.integer(int), Inversion = bestPick - 1L)
}


collapseEnharmonicSets <- function(notes) {
  # notes is.data.table with columns LO5th and Group
  
  .notes <- notes[!is.na(LO5th)]
  
  .notes[ , Range := diff(range(LO5th)), by = Group]
  
  while (.notes[ , any(Range >= 7L)]) {
    .notes[Range >= 7L , LO5th := {
      if(abs(min(LO5th) - mean(LO5th)) > abs(max(LO5th) - mean(LO5th))) { 
        ifelse(LO5th == min(LO5th), LO5th + 12L, LO5th) 
        } else {
          ifelse(LO5th == max(LO5th), LO5th - 12L, LO5th)}
      }, by= Group]
    
    
    .notes[ , Range := {
      newrange <- diff(range(LO5th))
      
      if (newrange < Range[1]) newrange else 0L
      }, by = Group]
  }
  
  .notes[ , Range := NULL]
  
  notes <- rbind(notes[is.na(LO5th)], .notes)
  setorder(notes, Group)
  notes
}

completeExtensions <- function(extension, full = FALSE) {
  empty <- extension == 0L
  missingfifth <- (extension %% 8L) < 4L
  extension[!empty & missingfifth] <- extension[!empty & missingfifth] + 4L
  # missingthird <- (extension %% 4L) < 2L
  
  hasseventh <- (extension %% 16L) >= 8L
  
  extension <- ifelse(!empty & hasseventh, 15L + extension - (extension %% 16L), extension)
  extension
  # as.integer((2 ^ ceiling(log(1L + extension, 2L))) - 1L)
  
  
}

figurationFill <- function(species, third, step, Explicit, ...) {
  # This function "fills" in missing (implicit) tertian degrees in a sonority.
  # For example, in IV6, there is an implicit 3.
    
    newthird <- if (length(third) == 0L) {
      
      0L:2L 
      
    } else {
      
      newthird <- if (all(step <= 3L)) 0:max(third) else 0L:max(2L, max(third))
      
      if (all(step == 5L) && species[1] == "") newthird <- setdiff(newthird, 1L) 
      gaps <- diff(step[Explicit])
      skips <- gaps > 2L & head(species[Explicit] == '', -1L)
      if (any(skips)) for (g in gaps[gaps > 2L]) newthird <- setdiff(newthird, third[Explicit][which(skips & gaps == g)] + (1L:((g - 2L) / 2L)))
      newthird
      
    }
    
    newaccidentals <- species[match(newthird, third)]
    newaccidentals[is.na(newaccidentals)] <- ""
    
    data.table(species = newaccidentals, step = newthird * 2L + 1L, third = newthird, 
               new = !newthird %in% third, Explicit = FALSE)
}

parseFiguration <- function(str, figureFill = TRUE, flat = 'b', qualities = FALSE, ...) {
  
  # str[str == ''] <- '35'
  
  makeRE <- partialApply(makeRE.tonalChroma, step.labels = 13:1, 
                         parts = c('species', 'step'),
                         collapse = TRUE)
  
  figures <- stringr::str_extract_all(str, makeRE(..., collapse = TRUE, flat = flat, qualities = qualities, quality.required = FALSE)[[1]], simplify = FALSE)
 
  figures <- lapply(figures, REparse, res = makeRE(..., collapse = FALSE, flat = flat, qualities = qualities, quality.required = FALSE))
  
  lapply(figures, 
         \(parsedfig) {
           parsedfig <- if(!is.null(parsedfig)) as.data.table(parsedfig) else data.table(species = character(2L), step = c('5', '3'))
           parsedfig[ , Explicit := TRUE]
           
           if (!any(parsedfig$step == '1')) parsedfig <- rbind(parsedfig, data.table(species = '', step = '1', Explicit = FALSE)) 
           
           
           ## 
           parsedfig[ , step := as.integer(step)]
           parsedfig[ , third := steps2thirds(step, integer(length(step)))]
           #
           parsedfig <- parsedfig[!duplicated(third)]
           inversion <- parsedfig[step %in% c(1L, 8L, 15L), third[1]]
           
           # extensions
           setorder(parsedfig, step)
           
           if (figureFill) parsedfig <- do.call(figurationFill, parsedfig)
           
           extensionInt <- parsedfig[ , as.integer(sum(2^third))]
           #
           data.table(Inversion   = inversion, 
                      Extension   = extensionInt, 
                      Degrees     = list(parsedfig$step),
                      Accidentals = list(parsedfig$species))
           
           
         }) |> do.call(what = 'rbind')
}


### Chord representations ####  

roman2tset <- function(x, Key = dset(0,0), augment = '+', diminish = 'o', implicitSpecies = FALSE, ...) {
  Key <- CKey(Key)
  REparse(x,
          makeRE.roman(..., diminish = diminish, augment = augment, collapse = FALSE),
          parse.exhaust = FALSE, parse.strict = FALSE,
          toEnv = TRUE)  # adds accidental numeral triadalt figurations to the environment
  root <- tonalChroma2tint(paste0(accidental, toupper(numeral)), useKey = TRUE,
                           parts = c('species', 'step'), qualities = FALSE,
                           implicitSpecies = implicitSpecies,
                           step.labels = c('I', 'II', 'III', 'IV', 'V', 'VI', 'VII'),
                           Key = Key, ...)@Fifth
  
  figurations <- parseFiguration(figurations)
  ### quality of degress
  # extension qualities
  qualities <- extensions2qualities(root, figurations, triadalt, Key = Key, flat = 'b', diminish = diminish, augment = augment, ...)
  # incorporate quality of triad
  qualities <- local({
    triad <- rep('M', length(numeral))
    triad[numeral == tolower(numeral)] <- 'm'
    triad[triadalt == diminish] <- diminish
    triad[triadalt == augment]  <- augment
    
    triad2sciQuality(triad, qualities, incomplete = '', diminish = diminish, augment = augment, ...)
  })
  
  qualitytset <-  sciQualities2tset(qualities, ..., diminish = diminish, augment = augment)
  
  # if 1 is altered!
  root <- root + setNames(c(-7L, 7L, 0L), c(diminish, augment, 'P'))[stringr::str_sub(qualities, 1L, 1L)]
    
  ###
 
  output <- tset(root, 
                 root + getMode(qualitytset),
                 alterations = qualitytset@Alteration,
                 extension = figurations$Extension,  
                 inversion = figurations$Inversion)

  # if (implicitSpecies) output <- output + Key
  output
  
}


harm2tset <- function(x, Key = dset(0,0), 
                      figurationArgs = list(),
                      augment = '+', diminish = 'o', implicitSpecies = TRUE, ...) {
  Key <- CKey(Key)
  REparse(x,
          makeRE.harm(..., collapse = FALSE),
          parse.exhaust = FALSE, parse.strict = FALSE,
          toEnv = TRUE)  # adds accidental numeral triadalt figurations and inversion to the environment
  
  Key <- romanNumeral2dset(stringr::str_sub(of, start = 2L), Key = Key, implicitSpecies = implicitSpecies, ...)
  Key[is.na(Key)] <- dset(0L, 0L)
  Key@Alteration[getMode(Key) == -3L & Key@Alteration == 0L] <- 1L # change minor to harmonic minor
  
  root <- tonalChroma2tint(paste0(accidental, toupper(numeral)), useKey = TRUE,
                           parts = c('species', 'step'), qualities = FALSE,
                           implicitSpecies = implicitSpecies,
                           step.labels = c('I', 'II', 'III', 'IV', 'V', 'VI', 'VII'),
                           Key = Key, ...)@Fifth
  
  # 
  figArgs <- list(diminish = 'D', augment = 'A', qualities = TRUE)
  figArgs[names(figurationArgs)] <- figurationArgs
  
  figurations <- do.call('parseFiguration', c(list(figurations), figArgs))
  ### quality of degress
  # extension qualities
  qualities <- do.call('extensions2qualities',
                       c(list(root, figurations, triadalt, Key = Key), figArgs)) 
  
  # incorporate quality of triad
  qualities <- local({
    triad <- rep('M', length(numeral))
    triad[numeral == tolower(numeral)] <- 'm'
    triad[triadalt == diminish] <- figArgs$diminish
    triad[triadalt == augment]  <- figArgs$augment
    
    triad2sciQuality(triad, qualities, incomplete = '', diminish = 'D', augment = 'A', ...)
  })
  
  qualitytset <-  sciQualities2tset(qualities, ..., diminish = 'D', augment = 'A')
  
  # if 1 is altered!
  root <- root + setNames(c(-7L, 7L, 0L), c(diminish, augment, 'P'))[stringr::str_sub(qualities, 1L, 1L)]
  root <- root + getRoot(Key)
  
  ###
  output <- tset(root, 
                 root + getMode(qualitytset),
                 alterations = qualitytset@Alteration,
                 extension = figurations$Extension, 
                 inversion = ifelse(inversion == '', 0L, match(inversion, letters) - 1L))
  
  # if (implicitSpecies) output <- output + Key
  output
  
}

sciQualities2tset <- function(str, inversion = 0L, ...) {
  
  chord <- stringr::str_pad(str, width = 7L, side = 'right', pad = '.')
  
  dset <- qualities2dset(chord, steporder = 4L, allow_partial = TRUE, ...)
  
  extension <- sapply(stringr::str_locate_all(str, '[^.]'), \(x) sum(as.integer(2L^(x[,  'start'] - 1L))))
  
  tset(dset@Root, dset@Signature, dset@Alteration, extension = extension, inversion = inversion)
  
}

tertian2tset <- function(x, Key = dset(0, 0), ...) {
    REparse(x,
            makeRE.tertian(..., collapse = FALSE), # makes tonalChroma, quality, inversion, and incomplete
            toEnv = TRUE) -> parsed
  
    Key <- diatonicSet(Key)
    
    root <- tonalChroma2tint(tonalChroma, parts = c('step', 'species'), qualities = FALSE, ...)@Fifth
    
    # qualities
    quality <- local({
      quality <- stringr::str_pad(quality, width = 5L, side = 'right', pad = '.')
      quality <- do.call('rbind', strsplit(quality, split = ""))
      triad <- quality[ , 1]
      extensions <- cbind('.', '.', '.', quality[ , -1L, drop = FALSE])
      
      triad2sciQuality(triad, extensions, incomplete, ...)
    })
    inversion <- ifelse(inversion == '', 0L, match(gsub('^/', '', inversion), c(1, 3, 5, 7, 2, 4, 6)) - 1L)
    
    (sciQualities2tset(quality,  inversion = inversion, ...) + tset(root, root, inversion = inversion)) - getRoot(Key)
    
}

figuredBass2tset <- function(x, ...) {
  REparse(x,
          makeRE.figuredBass(..., collapse = FALSE), # bass, bass.sep, figurations 
          toEnv = TRUE) -> parsed
  
  bass <- kern2tint(bass)
  
  figurations <- parseFiguration(figurations)
  
  tset <- .unlist(figurations[,
              {
                tints <- interval2tint(paste0(Accidentals[[1]], Degrees[[1]]), qualities = FALSE)
                tints <- tints - tints[1]
                inversion <- Inversion
                
                sciDegrees <- paste(tint2specifier(tints, qualities=T, explicitNaturals = TRUE), collapse = '')
                list(list(sciQualities2tset(sciDegrees, minor = 'm', diminish = 'd', augment = 'A', major = 'M',
                                            inversion = inversion) - tints[inversion + 1L]))
              },
              by = 1:nrow(figurations)]$V1)
  
  tset + bass
  
  
}

chord2tset <- function(x, ..., major = 'maj', minor = 'min', augment = 'aug', diminish = 'dim', flat = 'b') {
  # preprocessing
  x <- stringr::str_replace(x, '[Mm]aj7', 'majn7')
  x <- stringr::str_replace(x, 'maj([91])', 'majn7\\1')
  
  #
  REparse(x,
          makeRE.chord(..., major = major, minor = minor, augment = augment, diminish = diminish,
                       flat = flat, collapse = FALSE), # makes tonalChroma, bass, quality, figurations
          toEnv = TRUE) -> parsed
  
  quality[quality == ''] <- major
  quality <- setNames(c('M', 'm', 'A', 'd'), c(major, minor, augment, diminish))[quality]
  makeRE.figs <- partialApply(makeRE.tonalChroma, step.labels = 13:1, 
                         parts = c('species', 'step'), qualities = FALSE,
                         collapse = TRUE)
  
  figurations <- stringr::str_extract_all(figurations, makeRE.figs(..., collapse = TRUE, flat = flat)[[1]], simplify = FALSE)
  
  figurations <- lapply(figurations, REparse, res = makeRE.figs(..., collapse = FALSE, flat = flat))
  sciQualities <- do.call('rbind',
                       lapply(figurations,
                              \(fig) {
                                step <- fig[ , 'step']
                                
                                if (is.null(step)) return(c('.', '.', '.', '.'))
                                step <- ifelse(step %in% c('9', '11', '13'), c('2', '4', '6')[match(step, c('9', '11', '13'))], step)
                                fig <- paste0(fig[ , 'species'], step)
                                
                                quals <-  tint2specifier(deg2tint(fig, flat = flat, parts = c('species', 'step'),
                                                                  Key = dset(0L, -1L), implicitSpecies = TRUE), qualities = TRUE, explicitNaturals = TRUE)
                                
                                
                                extensions <- c('7' = '.', '2' = '.', '4' = '.', '6' = '.')
                                extensions[step] <- quals
                                if (extensions['7'] == '.' && any(step == c('2', '4', '6'))) extensions['7'] <- 'm'
                                extensions
                                
                              }))
  sciQualities <- do.call('paste0', as.data.frame(sciQualities))
  
  tset <- tertian2tset(paste0(tonalChroma, quality, sciQualities), flat = flat, ...)
  
  if (any(bass != '')) {
    hasbass <- bass != ''
    
    bassint <- getFifth(kern2tint(stringr::str_sub(bass[hasbass], start = 2L))) - getFifth(kern2tint(tonalChroma[hasbass]))
    tset@Inversion[hasbass] <- c(0L, 2L, 4L, 6L, 1L, 3L, 5L)[bassint %% 7L + 1L]
  }
  
  
  tset
  
}

harte2tset <- function(x,  ..., major = 'maj', minor = 'min', augment = 'aug', diminish = 'dim', flat = '-') {
  REparse(x,
          makeRE.harte(..., major = major, minor = minor, augment = augment, diminish = diminish,
                       flat = flat, collapse = FALSE), # makes tonalChroma, figqual, bass
          toEnv = TRUE) -> parsed
  
  # shorthand translation
  shorthands <- local({ 
    shorthands <- c(maj = '3,5', min = 'b3,5', aug = '3,#5', dim = 'b3,b5',
                    '7' = '3,5,b7', maj7 = '3,5,7', min7 = 'b3,5,b7', dim7 = 'b3,b5,bb7', hdim7 = 'b3,b5,b7', minmaj7 = 'b3,5,7',
                    maj6 = '3,5,6', min6 = 'b3,5,6',
                    '9' = '3,5,b7,9', maj9 = '3,5,7,9', min9 = 'b3,5,b7,9', 
                    sus2 = '2,5', sus4 = '4,5')
    shorthands <- setNames(paste0('(1,', shorthands, ')'), names(shorthands))
    
    names(shorthands) <- gsub('maj', major, names(shorthands))
    names(shorthands) <- gsub('min', minor, names(shorthands))
    names(shorthands) <- gsub('aug', augment, names(shorthands))
    names(shorthands) <- gsub('dim', diminish, names(shorthands))
    
    shorthands
    
    
  })
   
   
  fig <- figqual
  fig[!grepl('^\\(', figqual)] <- shorthands[figqual[!grepl('^\\(', figqual)]]
  
  # translate fig to tertian
  fig <- gsub('^\\(1,', '', gsub('\\)$', '', fig))
  fig <- strsplit(fig, split = ',')
  tertian <- c('P', rep('.', 6))
  ind <- c('3' = 2L, '5' = 3L, '7' = 4L,
           '2' = 5L, '9' = 5L,
           '4' = 6L, '11' = 6L,
           '6' = 7L, '13' = 7L)
  tertian <- sapply(unique(fig),
         \(fig) {
           qualities <- tint2specifier(interval2tint(fig, qualities = FALSE, flat = 'b'), qualities = TRUE, explicitNaturals = TRUE)
           
           fig <- gsub('[b#]+', '', fig)
           tertian[ind[fig]] <- qualities
           paste(tertian, collapse = '')
           
         })
  
  
  tset <- sciQualities2tset(tertian, augment = 'A', diminish = 'd')[match(fig, unique(fig))]
  
  if (any(bass != '')) {
    hasbass <- bass != ''
    
    tset@Inversion[hasbass] <- ind[gsub('\\/b?', '', bass[hasbass])] - 1L
    
  }
  
  tset + kern2tint(tonalChroma, flat = flat)
  
}

##... Numbers

integer2tset <- function(x) tset(x, x)

## Chord Parsing Dispatch ######################################


### Parse 2tset generic and methods ####

#' @rdname chordParsing
#' @export
tertianSet <- function(...) UseMethod('tertianSet')

#' @rdname chordParsing
#' @export
tertianSet.tertianSet <- function(x, ...) x

#' @rdname chordParsing
#' @export
tertianSet.logical <- function(x, ...) vectorNA(length(x), 'tertianSet')

#' @rdname chordParsing
#' @export
tertianSet.NULL <- function(x, ...) tset(c(), c())

#### Numbers ####


#' @rdname chordParsing
#' @export
tertianSet.numeric <- \(x) integer2tset(as.integer(x))

#' @rdname chordParsing
#' @export
tertianSet.integer <- integer2tset


#### Characters ####


# 
# 
# 
# mapoftset <- function(str, Key = NULL, ..., split = '/') {
#   Key <- Key %||% dset(0L, 0L)
#   Key <- rep(Key, length.out = length(str))
#   
#   parts <- strPartition(str, split = split)
#   Keys <- parts[-1]
#   if (length(Keys) > 0L) {
#     Keys[] <- head(Reduce(\(x, y) {
#       y[!is.na(x)] <- char2dset(x[!is.na(x)], Key = y[!is.na(x)], ...)
#       y
#     }, right = TRUE, 
#     init = dset(integer(length(str)), 0L), 
#     Keys, 
#     accumulate = TRUE), -1L) 
#     
#   } else {
#     Keys <- list(dset(integer(length(str)), 0))
#   }
#   
#   ofMode <- CKey(Keys[[1]])
#   root <- Reduce('+', lapply(Keys, getRoot))
#   ofKey <- ofMode + dset(root, root)
#   
#   tset <- char2tset(parts$base, Key = Key + ofKey, ...)
#   tset + dset(root, root, 0L)
# }

#' @rdname chordParsing
#' @export
tertianSet.character <- makeHumdrumDispatcher(list('harm', makeRE.harm,     harm2tset),
                                              list('roman',  makeRE.roman,    roman2tset),
                                              list('figuredBass', makeRE.figuredBass, figuredBass2tset),
                                              list('harte', makeRE.harte, harte2tset),
                                              list('any',  makeRE.tertian,  tertian2tset),
                                              list('any',  makeRE.chord,    chord2tset),
                                              funcName = 'tertianSet.character',
                                              outputClass = 'tertianSet')
  

#' @rdname chordParsing
#' @export
tertianSet.factor <- function(x, Exclusive = NULL, ...) {
  levels <- levels(x)
  
  tints <- tertianSet.character(levels, Exclusive = Exclusive, ...)
  
  c(tset(NA), tints)[ifelse(is.na(x), 1L, 1L + as.integer(x))]
}

#' @rdname chordParsing
#' @export
tertianSet.token <- function(x, Exclusive = NULL, ...) {
  tertianSet.character(as.character(x@.Data), Exclusive = Exclusive, ...) # %||% getExclusive(x), ...)
}


#' @export
setMethod('as.character', signature = c('tertianSet'), function(x) tset2tertian(x))

#### setAs tertianSet ####


setAs('integer', 'tertianSet', function(from) integer2tset(from))
setAs('numeric', 'tertianSet', function(from) integer2tset(as.integer(from)))
setAs('character', 'tertianSet', function(from) {
  output <- tset(rep(NA, length(from)))
  if (any(!is.na(from))) output[!is.na(from)] <- tertianSet.character(from[!is.na(from)])
  output
  })
setAs('matrix', 'tertianSet', function(from) tertianSet(c(from)) %<-matchdim% from)
setAs('logical', 'tertianSet', function(from) tset(rep(NA, length(from))) %<-matchdim% from)

setAs('diatonicSet', 'tertianSet', function(from) dset(from@Root, from@Signature, from@Alterations) %<-matchdim% from)
setAs('tertianSet', 'diatonicSet', function(from) tset(from@Root, from@Signature, from@Alterations) %<-matchdim% from)




 
###################################################################### ### 
# Translating Chord Representations (x2y) ################################
###################################################################### ### 


## Chord function documentation ####



#' Parsing and deparsing chord information
#' 
#' These functions can be used to extract and "translate," or otherwise modify, data representing tertian harmony information.
#' The functions are:
#' 
#' + Jazz/Pop
#'   + [chord()]
#'   + [harte()]
#' + Classical
#'   + [figuredBass()]
#'   + [tertian()]
#'   + *Roman Numerals*
#'     + [harm()]
#'     + [roman()]
#' 
#' @seealso To better understand how these functions work, read about how tertian harmonies are 
#' [parsed][chordParsing] and [deparsed][chordDeparsing].
#' 
#' @name chordFunctions
NULL


## Chord transform maker ####


makeChordTransformer <- function(deparser, callname, outputClass = 'character', removeArgs = NULL, extraArgs = alist()) {
  # this function will create various pitch transform functions
  
  autoArgTable <<- rbind(autoArgTable, 
                         data.table(Argument = 'Key', Type = 'Keyed', Function = callname, Expression = list(quote(Key))))
  
  deparser <- rlang::enexpr(deparser)
  callname <- rlang::enexpr(callname)
  
  
  args <- c(alist(x = , 
                  ... = , # don't move this! Needs to come before other arguments, otherwise unnamed parse() argument won't work!
                  Key = NULL,
                  transposeArgs = list(),
                  parseArgs = list(), 
                  inPlace = FALSE),
            extraArgs)
  if (!is.null(removeArgs)) args <- args[!names(args) %in% removeArgs]
  
  fargcall <- setNames(rlang::syms(names(args[-1:-2])), names(args[-1:-2]))
  
  rlang::new_function(args, rlang::expr( {
    # parse out args in ... and specified using the syntactic sugar parse() or tranpose()
    # parse out args in ... and specified using the syntactic sugar parse() or transpose()
    c('args...', 'parseArgs', 'transposeArgs') %<-% specialArgs(rlang::enquos(...), 
                                                                parse = parseArgs, transpose = transposeArgs)
    formalArgs <- list(!!!fargcall)
    namedArgs <- formalArgs[.names(formalArgs) %in% .names(as.list(match.call())[-1])]
    # There are four kinds of arguments: 
    # ... arguments (now in args...), 
    # FORMAL arguments, if specified (now in namedArgs)
    # parseArgs
    # transposeArgs
    
    # Exclusive
    parseArgs$Exclusive <- parseArgs$Exclusive %||% args...$Exclusive 
    
    parseArgs   <- pitchArgCheck(parseArgs, !!callname)
    deparseArgs <- pitchArgCheck(c(args..., namedArgs), !!callname)
    
    # Key
    Key     <- diatonicSet(Key %||% dset(0L, 0L))
    fromKey <- diatonicSet(transposeArgs$from %||% Key)
    toKey   <- diatonicSet(transposeArgs$to   %||% Key)
    
    parseArgs$Key   <- fromKey
    deparseArgs$Key <- toKey 
    
    if (!is.null(transposeArgs$from)) transposeArgs$from <- CKey(fromKey)
    if (!is.null(transposeArgs$to))   transposeArgs$to   <- CKey(toKey)
    
    # memoize % deparse
    memoize <- args...$memoize %||% TRUE
    deparse <- args...$deparse %||% TRUE
    
    # Parse
    parsedTset <- do(tertianSet, c(list(x), parseArgs), memoize = memoize, outputClass = 'tertianSet')
    if (length(transposeArgs) > 0L && is.tertianSet(parsedTset)) {
      parsedTset <- do(transpose.tertianSet, c(list(parsedTset), transposeArgs))
    }
    
    deparseArgs <- c(list(parsedTset), deparseArgs)
    output <- if (deparse && is.tertianSet(parsedTset))  do(!!deparser, deparseArgs, 
                                                            memoize = memoize, 
                                                            outputClass = !!outputClass) else parsedTset
    if (deparse && !is.null(output)) {
      dispatch <- attr(parsedTset, 'dispatch')
      if (inPlace) output <- rePlace(output, attr(parsedTset, 'dispatch'))
      
      if (!is.null(parseArgs$Exclusive)) humdrumRattr(output) <- list(Exclusive = makeExcluder(dispatch$Exclusives, !!callname))
    }
    
    output
    
   
  }))
  
  
}





### Chord functions ####

#' @param x ***An `atomic` vector.***
#' 
#' The `x` argument can be any ([atomic][base::vector]) vectors
#' 
#' @param Key ***The diatonic key used by the parser, deparser, and transposer.***
#' 
#' Defaults to `NULL`, which is interpreted as C major.
#' 
#' Must be a `diatonicSet` or something coercable to `diatonicSet`; must be either length `1` or `length(x)`.
#' 
#' @param parseArgs ***An optional list of arguments to the [chord parser][chordParsing].***
#' 
#' Defaults to an empty `list()`.
#' 
#' Must be a `list` of named arguments to the [chord parser][chordParsing].
#' 
#' @param transposeArgs ***An optional list of arguments passed to a [transpose()] call.***
#' 
#' Defaults to an empty `list()`.
#' 
#' Must be a `list` of named arguments to [transpose()].
#' 
#' @param inPlace ***Should non-chord information be retained in the output string.***
#' 
#' Defaults to `FALSE`.
#' 
#' Must be a singleton `logical` value: an on/off switch.
#' 
#' @name chordFunctions
NULL

#' "Pop/Jazz" chord symbols
#' 
#' These functions outputs jazz/pop-style chord symbols.
#' There is no universal standard for how to notate such chord symbols, in particular in plain text.
#' The `chord()` function outputs a chord symbol representation roughly consistent with "standard practices."
#'
#' For more rigorous, consistent work, we recommend the [Harte](https://github.com/Computational-Cognitive-Musicology-Lab/Star-Wars-Thematic-Corpus) notation,
#' which is the standard used by MIREX, etc.
#' The `harte()` function will output standard Harte symbols.
#' 
#' @examples
#' romanNumerals <- c('2I', '2IV7', '1V', '2vi', '2-VI', '2iio7', '2Vb9')
#' 
#' chord(romanNumerals)
#' chord(romanNumerals, Key = 'A:')
#' 
#' \dontrun{
#' B075 <- readHumdrum(humdrumRroot, "HumdrumData/BeethovenVariations/B075_00_05_a.krn")
#' with(B075[[ , 2]], chord(Token))
#' } 
#' 
#' @inheritParams chordFunctions
#' @export 
chord <- makeChordTransformer(tset2chord, 'chord')

#' @rdname chord
#' @export
harte <- makeChordTransformer(tset2harte, 'harte')

#' Figured bass representation of harmony
#' 
#' This function outputs a [figured bass](https://en.wikipedia.org/wiki/Figured_bass)
#' representation of a tertian harmony.
#' 
#' @examples
#' romanNumerals <- c('2I', '2IV7', '1V', '2vi', '2-VI', '2iio7', '2Vb9')
#' 
#' figuredBass(romanNumerals)
#' 
#' tertian <- c('CM', 'CMm/3', 'FM', 'Fm', 'D-MM', 'GMmm')
#' 
#' figuredBass(tertian)
#' 
#' \dontrun{
#' B075 <- readHumdrum(humdrumRroot, "HumdrumData/BeethovenVariations/B075_00_05_a.krn")
#' with(B075[[ , 2]], figuredBass(Token))
#' }
#' 
#' @inheritParams chordFunctions
#' @export 
figuredBass <- makeChordTransformer(tset2figuredBass, 'figuredBass')

#' Roman numeral representations of harmony
#' 
#' These functions output [roman numeral](https://en.wikipedia.org/wiki/Roman_numeral_analysis)
#' representations of a tertian harmony.
#' The `**harm` representation is the most widely used standard for roman numeral notation in humdrum data.
#' Unlike traditional roman numerals, `**harm` does not indicate inversions with figuration, using lowercase letters
#' (`a`, `b`, `c`, etc.) instead.
#' The `roman` function however does output (relatively) traditional figures.
#' 
#' @examples
#' tertian <- c('AM', 'AMm/3', 'DM', 'Dm', 'B-MM', 'AM/5', 'EMmm')
#'
#' harm(tertian, Key = 'A:')
#' roman(tertian, Key = 'A:')
#' 
#' \dontrun{
#' B075 <- readHumdrum(humdrumRroot, "HumdrumData/BeethovenVariations/B075_00_05_a.krn")
#'
#' with(B075[[ , 2]], harm(Token))
#' with(B075[[ , 2]], roman(Token))
#' }
#' 
#' @inheritParams chordFunctions
#' @export 
harm <- makeChordTransformer(tset2harm, 'harm')



#' Roman numeral representation of harmony
#' 
#' The output format of `roman()` is very similar to `**harm`.
#' The main difference is that inversions are indicated using traditional figures
#' , like `653`, instead of `**harm`'s simpler system (using letters).
#' So, for example, if we take the input `E7/B` in the key of A major, we'll get:
#'
#' + `harm('E7/B', Key = 'A:')` => `"V7c"`
#' + `roman('E7/B', Key = 'A:')` => `"V643"`
#' 
#' @rdname harm
#' @export 
roman <- makeChordTransformer(tset2roman, 'roman')


#' Tertian quality chord representation
#' 
#' This functions a generic form of tertian harmony representation, commonly used in music theory.
#' In this representation, the root of a chord is indicated as **kern, followed by one or more 
#' quality indicators, like `"CMM"` (C major seventh).
#' 
#' @details 
#' 
#' The first quality after the root indicates the quality of the triad.
#' Subsequent qualities, if present, indicate the quality of the 7th, 9th, 11th, and 13th respectively.
#' Some examples:
#' 
#' + `M`: major triad
#' + `Mm`: dominant-seventh chord
#' + `MM`: major-seventh chord
#' + `Mmm`: dominant-seventh-with-flat-9 chord.
#' + `oo`: fully-diminished-seventh chord.
#' 
#' Missing extensions can be indicated in their position using `.`.
#' For example, `E-Mm.P` indicates a E-flat dominant-11th chord with no 9th.
#' Missing members of the triad can be indicated by specifying either `5` or `3` immediately after the root, but before any
#' quality indicators.
#' For example, `C5M` indicates a C major chord with no 3rd, while `G3mm` indicates a G-minor-seventh chord with missing 5th.
#' 
#' The default quality indicators are `P` (perfect), `M` (major), `m` (minor), `o` (diminished), or `+` (augmented), but these
#' can be overridden by calls to their respective arguments: for example, `tertian('Cdim', diminish = 'd')`.
#' 
#' @section Inversions:
#' 
#' Inversions are indicated with slash notation, with the scale degree to the right of the slash.
#' For example, a first-inversion A major chord would be `AM/3`.
#' 
#' @examples
#' romanNumerals <- c('2I', '2IV7', '1V', '2vi', '2-VI', '2iio7', '2Vb9')
#' 
#' tertian(romanNumerals)
#' tertian(romanNumerals, Key = 'A:')
#' 
#' \dontrun{
#' B075 <- readHumdrum(humdrumRroot, "HumdrumData/BeethovenVariations/B075_00_05_a.krn")
#' with(B075[[,2]], tertian(Token))
#' results
#' }
#' 
#' @inheritParams chordFunctions
#' @export 
tertian <- makeChordTransformer(tset2tertian, 'tertian')

### humdrumR methods ----


#' @exportS3Method roman default
#' @exportS3Method roman humdrumR
humdrumRmethods('roman')
#' @exportS3Method figuredBass default
#' @exportS3Method figuredBass humdrumR
humdrumRmethods('figuredBass')
#' @exportS3Method harm default
#' @exportS3Method harm humdrumR
humdrumRmethods('harm')
#' @exportS3Method chord default
#' @exportS3Method chord humdrumR
humdrumRmethods('chord')
#' @exportS3Method harte default
#' @exportS3Method harte humdrumR
humdrumRmethods('harte')
#' @exportS3Method tertian default
#' @exportS3Method tertian humdrumR
humdrumRmethods('tertian')

###################################################################### ### 
# Manipulating tertian sets ##############################################
###################################################################### ### 



## Extracting pitches #####



### Line of Fifths ####

#' @export
setMethod('LO5th', 'tertianSet',
          function(x) {
            tset <- x
            
            # if (is.null(Key)) Key <- dset(0, 0)
            # tset <- tset + tset(getRoot(Key), getSignature(Key), cardinality = 0L)
            LO5ths <- callNextMethod(tset, steporder = 4L, inversion = getInversion(x))
            thirds <- getExtensions(tset)
            LO5ths <- LO5ths * thirds
            LO5ths[!thirds] <- NA_integer_
            
            rownames(LO5ths) <- tint2tonalChroma(tint( , getRoot(x)), qualities = FALSE,
                                                 step.labels = c('C', 'D', 'E', 'F', 'G', 'A', 'B'),
                                                 parts = c('step', 'species'))
            
            LO5ths
            
          })



## Extracting chords ----

# Algorithm:
# 
# G  Bb Db  F A   C  E  G  B  D  F# A C
#                 C  E  G
#                 E  G              C    
#           C     G                 E
#
#                 C  E  G  B
#                 E  G  C           C
#           C     G  B              E
#
#                 C  E  G  Bb 
# Bb              E  G              C
#           C     G  Bb             E
#                 Bb          C  E  G
#
#           F     C     G
#           C     G        F
#                 F     C    G
# G  Bb D   F  A  C  E  G  B  D  F# A  C
# Gb Bb Db  F  Ab C  Eb G  Bb D  F  A  C 
# G  B  D   F# A  C# E  G# B  D# F# A# C#
#       D#  F# A# C# E# G#
# 2 6 10 -7 -3 1 5 9 13 -4 0  4 8 12 -5 -1 3 7 11 -6 -2    
        
#' Interpret tertian sonorities from set(s) of notes.
#' 
#' The `sonority()` function accepts vectors of notes, usually
#' grouped into multiple chords by a `groupby` argument, and interprets
#' those notes as a tertian sonority.
#' Chords are output using the representation indicated by the `deparser` argument.
#' By default, [with/within.humdrumR][withinHumdrum] will automatically pass
#' `sonority` the `groupby` argument `groupby = list(Piece, Record)`,
#' so chords are estimated for each record in the dataset.
#'
#' @details 
#' 
#' 
#' If `inPlace = TRUE`, sonority()` returns vectorized output,
#' with the output matching the length of the input vector.
#' By default, `fill = FALSE`, and each output chord is repeated to align with 
#' the notes of the chord.
#' If `fill = FALSE`, each chord is returned only once, but padded
#' with null tokens to match length of the input.
#' Finally, if `inPlace = FALSE` only one chord is returned for each group in `groupby`.
#' 
#' If `inversions = TRUE`, the notes are interpreted
#' in the chordal inversion that is most compact (triad like)
#' on the circle of thirds.
#' If `inversions = FALSE`, the lowest note is always interpreted as
#' the root.
#' 
#' If `incomplete = TRUE`, incomplete chords are returns as they are,
#' so you might see things like "C7no5" (seventh chord with no fifth).
#' If `incomplete = FALSE`, `sonority()` will (attempt) to fill in missing 
#' but "implied" triad notes, note like missing 5ths.
#'
#' By default, `sonority()` will interpret the spelling of notes strictly, so a
#' "mispelled" triad, like *B, E♭, F♯* will be interpreted as something weird---in this case
#' an augmented *Eb* chord with no third and a sharp 9!
#' Note that in the case of [cross relations](https://en.wikipedia.org/wiki/False_relation)---for example,
#'  *B♭* **and** *B♮* in the same chord---`sonority()`
#' will simply ignore the later species that appears.
#' However, if `enharmonic = TRUE`, `sonority()` will reinterpret input notes
#' by collapsing them into a single diatonic set on the circle-of-fifths.
#' This means that the set *B, Eb, F♯* will be interpreted as *B, D♯, F♯*
#' and the set *B♭, D, F, B♮* will be interpreted as *B♭, D, F, C♭*.
#' 
#' 
#' @param x ***Input data, interpreted as pitches.***
#' 
#' This vector is interpreted as pitch information using [tonalInterval()].
#' 
#' @param deparser ***What output representation do you want?***
#' 
#' Defaults to [chord()].
#' 
#' Must be a [chord function][chordFunctions], like [roman()], [harm()] or [chord()].
#' 
#' @param Key ***The input key used by the deparser.***
#'
#' Defaults to `NULL`, indicating c major.
#' However, [with/within.humdrum][withinHumdrum] will automatically pass 
#' a `Key` field in the data to `sonority`, if there is one.
#'
#' Must be a `diatonicSet` or something coercable to `diatonicSet`; must be either length `1` or `length(x)`
#' 
#' Some chord parsers don't use `Key`, so it is irrelevant,
#' you *will* want to use a `Key` for roman numerals.
#' 
#' @param inversions ***Should we interpret note sets as inversions?***
#' 
#' Defaults to `TRUE`.
#' 
#' Must be a singleton `logical` value: an on/off switch.
#' 
#' @param incomplete ***Should we return incomplete chords?***
#' 
#' Defaults to `TRUE`.
#' 
#' Must be a singleton `logical` value: an on/off switch.
#' 
#' @param enharmonic ***Should pitches be interpreted enharmonically?***
#' 
#' Defaults to `FALSE`.
#' 
#' Must be a singleton `logical` value: an on/off switch.
#' 
#' @param inPlace ***Should the output always match the input?***
#' 
#' Defaults to `FALSE` is there is no `groupby` list; but `TRUE` if there is.
#' 
#' Must be a singleton `logical` value: an on/off switch. 
#' 
#' 
#' @param fill ***Should the output duplicate each chord for every note in the input?***
#' 
#' Defaults to `TRUE`.
#' 
#' Must be a singleton `logical` value: an on/off switch.
#' 
#' This argument only has an effect if `inPlace = TRUE`.
#' 
#' @examples 
#' 
#' sonority(c('C', 'e', 'g', 'b-'))
#' sonority(c('G', 'BB', 'd', 'f', 'a'))
#' 
#' sonority(c('C', 'b-', 'd', 'f'))
#' sonority(c('C', 'b-', 'd', 'f'), inversions = FALSE)
#' 
#' \dontrun{ 
#' chorales <- readHumdrum(humdrumRroot, 'HumdrumData/BachChorales/.*krn')
#' 
#' chorales <- within(chorales, dataTypes = 'Dd', ditto(Token) -> Token_dittoed) 
#' 
#' within(chorales, sonority(Token_dittoed))
#' within(chorales, sonority(Token_dittoed, deparser = harm))
#' }
#' @export
sonority <- function(x, deparser = chord, Key = NULL, 
                     inversions = TRUE, incomplete = TRUE,
                     enharmonic = FALSE,
                     inPlace = length(groupby) > 0, fill = TRUE, 
                     groupby = list(), ...) {
  
  inPlace <- inPlace # this is needed because the inPlace references groupby
  groupby <- if (length(groupby)) squashGroupby(groupby) else rep(1L, length(x))
 
  reorder(list(x = x, groupby = groupby, Key = Key), orderby = groupby, toEnv = TRUE)
  
  tints <- tonalInterval(x)
  tints <- tints[order(groupby, tint2semits(tints))]
  
  notes <- data.table(LO5th = LO5th(tints), Group = groupby)
  
  if (enharmonic) notes <- collapseEnharmonicSets(notes)
  
  notes[ , Third :=  LO5th2third(LO5th)]
  notes[ , Third := (Third - Third[!duplicated(Group)][Group]) %% 7L]
  notes[duplicated(cbind(Group, Third)), Third := NA_integer_]

  chords <- notes[ , list(Extension = as.integer(sum(2L^((Third - Third[1]) %% 7L), na.rm = TRUE))), by = Group]
  if (inversions) {
    chords <- chords[ , c(list(Group = Group), findBestInversion(Extension))]
  } else {
    chords[ , Inversion := 0L]
  }
  
  if (!incomplete) {
    chords[ , Extension := completeExtensions(Extension)]
  }
 
  notes[ , Inversion := chords[ , Inversion[notes$Group]]]
  # inversion <- findBestInversion(inversion)
  
  # root
  chords[ , Root := 0L]
  chords$Root[chords$Extension != 0L] <- notes[(Third + Inversion) %% 7L == 0L, LO5th]
  
  # signature
  chords <- notes[!is.na(LO5th), list(Sharpest = max(LO5th), Flatest = min(LO5th)), by = Group][chords, on = 'Group']
  
  
  chords[ , Range := Sharpest - Flatest]
  chords[ , FlatMargin := pmin((Flatest - Root) + 1L, 0)]
  chords[ , SharpMargin := pmax((Sharpest - Root) - 5L, 0L)]
  
  chords[ , Signature := as.integer(Root + ifelse(FlatMargin == 0L & SharpMargin == 1L, 1L, pmax(-6L, FlatMargin)))]
  
  
  if (any(chords$Range > 7L, na.rm = TRUE)) {
    notes <- notes[chords[Range > 7L], on = 'Group']
    notes[ , Sharp := LO5th > (5L + Signature)]
    notes[ , Flat := LO5th < (-1L + Signature)]
    notes[, Trit := -((LO5th - Root) + 2L) %% 7L]
    
    newchords <- chords[notes[!is.na(Third) , # NA Thirds are duplicated
                              list(Alteration = sum(-Flat * 3L^Trit + Sharp * 3L^Trit, na.rm = TRUE)), by = Group], on = 'Group']
    chords[ , Alteration := 0L]
    chords[Range > 7L] <- newchords
  }  else {
    chords[ , Alteration := 0L]
    
  }
  
  
  tset <- chords[, tset(Root, Signature, extension = Extension, inversion = Inversion, alterations = as.integer(Alteration))]
  
  if (!is.null(Key)) {
    Key <- tonalInterval(Key[changes(groupby)])@Fifth
    tset <- tset - Key
  }
  
  chords <- if (is.null(deparser)) tset else deparser(tset, ..., Key = Key)
  
  
  output <- if (fill)  {
    chords[groupby]
  } else {
    output <- vectorNA(length(chords), class(chords))
    output[changes(groupby)] <- chords
    output
  }
  
  output <- reorder(output)
  
  if (inPlace) output else output[changes(reorder(groupby))]
}
Computational-Cognitive-Musicology-Lab/humdrumR documentation built on Oct. 22, 2024, 9:28 a.m.