diff --git a/.gitignore b/.gitignore index c69928d..64a3e5c 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,6 @@ tests/testthat/cache/* www inst/shiny/cache .cache +.DS_Store +.idea +.ipynb_checkpoints diff --git a/DESCRIPTION b/DESCRIPTION index c21aba1..73d175d 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,7 +1,7 @@ Package: incon Type: Package Title: Computational Models of Simultaneous Consonance -Version: 0.4.1 +Version: 0.5.0 Author: person("Peter", "Harrison", email = "pmc.harrison@gmail.com", role = c("aut", "cre")) Depends: R (>= 3.4.0) Maintainer: Peter Harrison @@ -10,23 +10,22 @@ License: MIT + file LICENSE Encoding: UTF-8 LazyData: true Imports: + assertthat, Rdpack (>= 0.11.0), + gmp (>= 0.5.13.2), hrep (>= 0.10.0.9001), - bowl18 (>= 0.1.0), - corpdiss (>= 0.1.0.9000), - dycon (>= 0.2.0), - har18 (>= 0.1.0), - jl12 (>= 0.1.0), - parn88 (>= 0.2.0), - parn94 (>= 0.2.3), - stolz15 (>= 0.1.0), - wang13 (>= 0.1.1), + hht, checkmate (>= 1.9.4), magrittr (>= 1.5), purrr (>= 0.3.2), methods, + gtools, + Rcpp, + numbers, + phonTools, tibble (>= 2.1.3), dplyr (>= 0.8.3), + rlang (>= 0.4.0), zeallot (>= 0.1.0), utils RdMacros: Rdpack @@ -34,18 +33,19 @@ Suggests: testthat (>= 2.1.1), knitr (>= 1.23), DT (>= 0.5), - covr (>= 3.2.1) -RoxygenNote: 6.1.1 + covr (>= 3.2.1), + cowplot, + ggplot2 (>= 3.1.0.9000), + hcorp, + shiny, + shinydashboard, + shinyjs, + tuneR +RoxygenNote: 7.3.1 Remotes: pmcharrison/hrep, - pmcharrison/bowl18, - pmcharrison/corpdiss, - pmcharrison/dycon, - pmcharrison/har18, - pmcharrison/jl12, - pmcharrison/parn88, - pmcharrison/parn94, - pmcharrison/stolz15, - pmcharrison/wang13 + pmcharrison/hcorp VignetteBuilder: knitr Byte-Compile: yes +LinkingTo: + Rcpp diff --git a/Demo.ipynb b/Demo.ipynb new file mode 100644 index 0000000..3aba2ef --- /dev/null +++ b/Demo.ipynb @@ -0,0 +1,444 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "id": "b66440e4-d192-4ac9-9553-718cd2618059", + "metadata": {}, + "source": [ + "# incon package demo\n", + "\n", + "This notebook demonstrates the basic functionality of the 'incon' package.\n", + "To run code in this notebook, select the top cell in the notebook (i.e. this one,\n", + "then click the 'Run' button to run one cell at a time and advance to the next one.\n", + "You can change the content of cells and rerun them to get new results if you wish." + ] + }, + { + "cell_type": "code", + "execution_count": 87, + "id": "516871a1-57c0-4289-beb9-4201f3689465", + "metadata": {}, + "outputs": [], + "source": [ + "# Run this cell to load core packages\n", + "\n", + "suppressPackageStartupMessages({\n", + " library(glue)\n", + " library(incon)\n", + " library(dplyr, quietly = TRUE)\n", + " library(hrep, quietly = TRUE)\n", + "})\n", + "\n", + "display_chord <- abcR:::html_from_pi_chord" + ] + }, + { + "cell_type": "markdown", + "id": "756e4ee8-6108-4f52-a9be-a7e7b984da46", + "metadata": {}, + "source": [ + "The incon package provides a collection of implementations of consonance models.\n", + "These models are enumerated in the following table:" + ] + }, + { + "cell_type": "code", + "execution_count": 88, + "id": "673f4370-d2fa-4412-b31e-763b79a2ebc4", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\n", + "\t\n", + "\t\n", + "\n", + "\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\t\n", + "\n", + "
A tibble: 17 × 3
Model typeCitationLabel
<chr><chr><chr>
Periodicity/harmonicityGill & Purves (2009) gill_09_harmonicity
Periodicity/harmonicityHarrison & Pearce (2018) har_18_harmonicity
Periodicity/harmonicityMilne (2013) milne_13_harmonicity
Periodicity/harmonicityParncutt (1988) parn_88_root_ambig
Periodicity/harmonicityParncutt & Strasburger (1994)parn_94_complex
Periodicity/harmonicityStolzenburg (2015) stolz_15_periodicity
Interference Bowling et al. (2018) bowl_18_min_freq_dist
Interference Huron (1994) huron_94_dyadic
Interference Hutchinson & Knopoff (1978) hutch_78_roughness
Interference Parncutt & Strasburger (1994)parn_94_pure
Interference Sethares (1993) seth_93_roughness
Interference Vassilakis (2001) vass_01_roughness
Interference Wang et al. (2013) wang_13_roughness
Culture Johnson-Laird et al. (2012) jl_12_tonal
Culture Harrison & Pearce (2019) har_19_corpus
Numerosity Parncutt & Strasburger (1994)parn_94_mult
Composite Harrison & Pearce (2019) har_19_composite
\n" + ], + "text/latex": [ + "A tibble: 17 × 3\n", + "\\begin{tabular}{lll}\n", + " Model type & Citation & Label\\\\\n", + " & & \\\\\n", + "\\hline\n", + "\t Periodicity/harmonicity & Gill \\& Purves (2009) & gill\\_09\\_harmonicity \\\\\n", + "\t Periodicity/harmonicity & Harrison \\& Pearce (2018) & har\\_18\\_harmonicity \\\\\n", + "\t Periodicity/harmonicity & Milne (2013) & milne\\_13\\_harmonicity \\\\\n", + "\t Periodicity/harmonicity & Parncutt (1988) & parn\\_88\\_root\\_ambig \\\\\n", + "\t Periodicity/harmonicity & Parncutt \\& Strasburger (1994) & parn\\_94\\_complex \\\\\n", + "\t Periodicity/harmonicity & Stolzenburg (2015) & stolz\\_15\\_periodicity \\\\\n", + "\t Interference & Bowling et al. (2018) & bowl\\_18\\_min\\_freq\\_dist\\\\\n", + "\t Interference & Huron (1994) & huron\\_94\\_dyadic \\\\\n", + "\t Interference & Hutchinson \\& Knopoff (1978) & hutch\\_78\\_roughness \\\\\n", + "\t Interference & Parncutt \\& Strasburger (1994) & parn\\_94\\_pure \\\\\n", + "\t Interference & Sethares (1993) & seth\\_93\\_roughness \\\\\n", + "\t Interference & Vassilakis (2001) & vass\\_01\\_roughness \\\\\n", + "\t Interference & Wang et al. (2013) & wang\\_13\\_roughness \\\\\n", + "\t Culture & Johnson-Laird et al. (2012) & jl\\_12\\_tonal \\\\\n", + "\t Culture & Harrison \\& Pearce (2019) & har\\_19\\_corpus \\\\\n", + "\t Numerosity & Parncutt \\& Strasburger (1994) & parn\\_94\\_mult \\\\\n", + "\t Composite & Harrison \\& Pearce (2019) & har\\_19\\_composite \\\\\n", + "\\end{tabular}\n" + ], + "text/markdown": [ + "\n", + "A tibble: 17 × 3\n", + "\n", + "| Model type <chr> | Citation <chr> | Label <chr> |\n", + "|---|---|---|\n", + "| Periodicity/harmonicity | Gill & Purves (2009) | gill_09_harmonicity |\n", + "| Periodicity/harmonicity | Harrison & Pearce (2018) | har_18_harmonicity |\n", + "| Periodicity/harmonicity | Milne (2013) | milne_13_harmonicity |\n", + "| Periodicity/harmonicity | Parncutt (1988) | parn_88_root_ambig |\n", + "| Periodicity/harmonicity | Parncutt & Strasburger (1994) | parn_94_complex |\n", + "| Periodicity/harmonicity | Stolzenburg (2015) | stolz_15_periodicity |\n", + "| Interference | Bowling et al. (2018) | bowl_18_min_freq_dist |\n", + "| Interference | Huron (1994) | huron_94_dyadic |\n", + "| Interference | Hutchinson & Knopoff (1978) | hutch_78_roughness |\n", + "| Interference | Parncutt & Strasburger (1994) | parn_94_pure |\n", + "| Interference | Sethares (1993) | seth_93_roughness |\n", + "| Interference | Vassilakis (2001) | vass_01_roughness |\n", + "| Interference | Wang et al. (2013) | wang_13_roughness |\n", + "| Culture | Johnson-Laird et al. (2012) | jl_12_tonal |\n", + "| Culture | Harrison & Pearce (2019) | har_19_corpus |\n", + "| Numerosity | Parncutt & Strasburger (1994) | parn_94_mult |\n", + "| Composite | Harrison & Pearce (2019) | har_19_composite |\n", + "\n" + ], + "text/plain": [ + " Model type Citation Label \n", + "1 Periodicity/harmonicity Gill & Purves (2009) gill_09_harmonicity \n", + "2 Periodicity/harmonicity Harrison & Pearce (2018) har_18_harmonicity \n", + "3 Periodicity/harmonicity Milne (2013) milne_13_harmonicity \n", + "4 Periodicity/harmonicity Parncutt (1988) parn_88_root_ambig \n", + "5 Periodicity/harmonicity Parncutt & Strasburger (1994) parn_94_complex \n", + "6 Periodicity/harmonicity Stolzenburg (2015) stolz_15_periodicity \n", + "7 Interference Bowling et al. (2018) bowl_18_min_freq_dist\n", + "8 Interference Huron (1994) huron_94_dyadic \n", + "9 Interference Hutchinson & Knopoff (1978) hutch_78_roughness \n", + "10 Interference Parncutt & Strasburger (1994) parn_94_pure \n", + "11 Interference Sethares (1993) seth_93_roughness \n", + "12 Interference Vassilakis (2001) vass_01_roughness \n", + "13 Interference Wang et al. (2013) wang_13_roughness \n", + "14 Culture Johnson-Laird et al. (2012) jl_12_tonal \n", + "15 Culture Harrison & Pearce (2019) har_19_corpus \n", + "16 Numerosity Parncutt & Strasburger (1994) parn_94_mult \n", + "17 Composite Harrison & Pearce (2019) har_19_composite " + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "incon_models %>%\n", + " select(\"Model type\" = class, \"Citation\" = citation, \"Label\" = label)" + ] + }, + { + "cell_type": "markdown", + "id": "cc801eba-c34b-4440-a3ba-b55ae798b072", + "metadata": {}, + "source": [ + "The below code illustrates basic usage. First we choose a chord to analyse. This chord is specified in MIDI note numbers. If you are not familiar with MIDI note numbers, do have a look at this [information page](https://pmcharrison.github.io/intro-to-music-and-science/pitch.html#pitch-notation). In this example we are analysing a closed C major triad on middle C, comprising the notes C4 (60), E4 (64), and G4 (67).\n", + "\n", + "To run these models on your own chord, simply change the numbers below, then rerun the cell.\n", + "For example, replace `60, 64, 67` with `60, 63, 67` to get a C minor triad (as opposed to a C major triad).\n", + "\n", + "To select different models, refer to the table of implemented models above, then replace the \n", + "model selected in the `incon` function." + ] + }, + { + "cell_type": "code", + "execution_count": 89, + "id": "2a7c610b-ffec-483f-a819-1affb9aa4d09", + "metadata": { + "tags": [] + }, + "outputs": [ + { + "data": { + "text/html": [ + "\n", + "\n", + "\t\n", + "\t\t\n", + "\t\t\n", + "\t\n", + "\t\n", + "\t\t
\n", + " \n", + " \n", + " \n", + " \n", + "
\n", + "

\n", + "
\n", + " \n", + " \n", + "
\n", + "\t\n", + "\n" + ], + "text/plain": [ + "Shiny tags cannot be represented in plain text (need html)" + ] + }, + "metadata": { + "text/html": { + "isolated": true + } + }, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Roughness = 0.120242607949697 (higher values mean less consonance)\n", + "Relative period length = 2 (higher values mean less consonance) \n", + "Corpus scarcity = 0.780243332100067 (higher values mean less consonance) \n" + ] + } + ], + "source": [ + "chord <- c(60, 64, 67)\n", + "\n", + "display_chord(chord)\n", + "\n", + "roughness <- incon(chord, \"hutch_78_roughness\")\n", + "print(glue(\"Roughness = { roughness} (higher values mean less consonance)\"))\n", + "\n", + "period_length <- incon(chord, \"stolz_15_periodicity\")\n", + "print(glue(\"Relative period length = { period_length } (higher values mean less consonance) \"))\n", + "\n", + "corpus_scarcity <- incon(chord, \"har_19_corpus\")\n", + "print(glue(\"Corpus scarcity = { corpus_scarcity } (higher values mean less consonance) \"))" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "R", + "language": "R", + "name": "ir" + }, + "language_info": { + "codemirror_mode": "r", + "file_extension": ".r", + "mimetype": "text/x-r-source", + "name": "R", + "pygments_lexer": "r", + "version": "4.3.2" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} diff --git a/NAMESPACE b/NAMESPACE index 9ea3e41..67212bb 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -1,9 +1,119 @@ # Generated by roxygen2: do not edit by hand +S3method(as.character,fraction) +S3method(bowl18_min_freq_dist,default) +S3method(bowl18_min_freq_dist,fr_chord) +S3method(complex_sonor,default) +S3method(complex_sonor,parn94) +S3method(corpus_dissonance_table,corpus) +S3method(count_chords,corpus) +S3method(double,fraction) +S3method(expand_harmonics,fraction) +S3method(expand_harmonics,rational_chord) +S3method(fraction,numeric) +S3method(gcd,rational_chord) +S3method(half,fraction) +S3method(huron_1994,default) +S3method(huron_1994,int_vec) +S3method(huron_1994,pc_set) +S3method(jl_tonal_dissonance,default) +S3method(jl_tonal_dissonance,pc_set) +S3method(kl_div_from_uniform,smooth_spectrum) +S3method(lcm,rational_chord) +S3method(multiplicity,default) +S3method(multiplicity,parn94) +S3method(parn88,default) +S3method(parn88,pc_set) +S3method(parn94,default) +S3method(parn94,sparse_pi_spectrum) +S3method(pc_harmonicity,default) +S3method(pc_harmonicity,milne_pc_spectrum) +S3method(pc_harmonicity,pc_set) +S3method(peak,milne_pc_spectrum) +S3method(pitch_salience,default) +S3method(pitch_salience,parn94) +S3method(pitch_salience,pitch_salience) +S3method(print,corpus_dissonance_table) +S3method(pure_sonor,default) +S3method(pure_sonor,parn94) +S3method(rationalise_chord,pi_chord) +S3method(roughness_hutch,default) +S3method(roughness_hutch,sparse_fr_spectrum) +S3method(roughness_seth,default) +S3method(roughness_seth,sparse_fr_spectrum) +S3method(roughness_vass,default) +S3method(roughness_vass,sparse_fr_spectrum) +S3method(roughness_wang,default) +S3method(roughness_wang,sparse_fr_spectrum) +S3method(smooth_log_periodicity,default) +S3method(smooth_log_periodicity,pi_chord) +S3method(sweep_harmonic_template,milne_pc_spectrum) +S3method(sweep_harmonic_template,pc_set) +S3method(type,corpus_dissonance_table) +export(bowl18_min_freq_dist) +export(complex_sonor) +export(corpus_dissonance) +export(corpus_dissonance_table) +export(cosine_similarity) +export(count_chords) +export(demo_wang) +export(get_free_field_threshold) +export(get_overall_masking_level) +export(get_partial_masking_level) +export(get_pure_tone_audibility) +export(get_pure_tone_audible_level) +export(get_pure_tone_height) +export(get_tone_salience) +export(gill09_harmonicity) export(har_19_composite_coef) +export(huron_1994) +export(huron_1994_weights) +export(hutch_cbw) +export(hutch_dissonance_function) +export(hutch_g) +export(hutch_visualise) +export(hutch_y) export(incon) export(incon_models) +export(jl_rule_1) +export(jl_rule_2) +export(jl_rule_3) +export(jl_tonal_dissonance) +export(kl_div_from_uniform) export(list_models) +export(multiplicity) +export(parn88) +export(parn94) +export(parn94_params) +export(pc_harmonicity) +export(peak) +export(pitch_commonality) +export(pitch_distance) +export(pitch_salience) +export(pure_sonor) +export(root) +export(root_ambiguity) +export(root_support_weights) +export(roughness_hutch) +export(roughness_seth) +export(roughness_vass) +export(roughness_wang) +export(smooth_log_periodicity) +export(sweep_harmonic_template) +export(sweep_template) +export(type) +importFrom(Rcpp,sourceCpp) +importFrom(Rdpack,reprompt) importFrom(magrittr,"%>%") +importFrom(methods,"is") +importFrom(methods,.valueClassTest) +importFrom(methods,new) +importFrom(rlang,".data") +importFrom(stats,"approx") +importFrom(stats,"cor") +importFrom(stats,"fft") +importFrom(stats,cor) importFrom(tibble,tibble) +importFrom(utils,"capture.output") importFrom(zeallot,"%<-%") +useDynLib(incon, .registration = TRUE) diff --git a/NEWS.md b/NEWS.md index 9d5c38f..d300aa2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,8 @@ +# incon 0.5.0 + +- Consolidated all modelling dependencies into this package, + to avoid having to download so many GitHub repositories on installation. + # incon 0.4.1 - Addressing some minor dependency issues. diff --git a/R/RcppExports.R b/R/RcppExports.R new file mode 100644 index 0000000..42afe10 --- /dev/null +++ b/R/RcppExports.R @@ -0,0 +1,19 @@ +# Generated by using Rcpp::compileAttributes() -> do not edit by hand +# Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 + +mod <- function(x, base) { + .Call(`_incon_mod`, x, base) +} + +get_index <- function(x, index, offset) { + .Call(`_incon_get_index`, x, index, offset) +} + +cosine_similarity_cpp <- function(x, template_, offset) { + .Call(`_incon_cosine_similarity_cpp`, x, template_, offset) +} + +sweep_template_cpp <- function(x, template_) { + .Call(`_incon_sweep_template_cpp`, x, template_) +} + diff --git a/R/imports.R b/R/imports.R index bc27478..7f7bcf5 100644 --- a/R/imports.R +++ b/R/imports.R @@ -1,5 +1,35 @@ +## usethis namespace: start +#' @useDynLib incon, .registration = TRUE +## usethis namespace: end +NULL + +## usethis namespace: start +#' @importFrom Rcpp sourceCpp +## usethis namespace: end +NULL + #' @importFrom magrittr "%>%" NULL #' @importFrom zeallot "%<-%" NULL + +#' @importFrom methods "is" +NULL + +#' @importFrom stats "approx" "cor" "fft" +NULL + +#' @importFrom tibble tibble +NULL + +#' @importFrom utils "capture.output" +NULL + +#' @importFrom rlang ".data" +NULL + +`.` <- NULL + +#' @importFrom Rdpack reprompt +NULL diff --git a/R/model-bowl18.R b/R/model-bowl18.R new file mode 100644 index 0000000..21ff469 --- /dev/null +++ b/R/model-bowl18.R @@ -0,0 +1,236 @@ +#' Harmonicity +#' +#' Computes the harmonicity of a chord using the +#' percentage similarity measure of Gill & Purves (2009). +#' +#' @details +#' This percentage similarity measure corresponds to the percentage of the harmonics +#' that the chord holds in common with a harmonic series rooted on +#' the chord's fundamental frequency. +#' +#' While \insertCite{Gill2009;textual}{incon} originally presented this measure +#' to quantify the harmonicity of two-note chords (dyads) +#' \insertCite{Bowling2018;textual}{incon} subsequently demonstrated the application +#' of this measure to chords of arbitrary sizes. +#' +#' @note +#' The algorithm assumes that chord pitches are precisely +#' tuned to the just-tuned scale provided by Bowling et al. (2018). +#' This scale can be found at \code{\link{rational_scale}}. +#' +#' @param x Chord to analyse, +#' expressed as an integerish vector of MIDI note numbers, +#' or more generally as any valid input to \code{\link[hrep:pi_chord]{pi_chord}}. +#' @param tonic (Integerish scalar, default = 0L) +#' Tonic to use when defining the just-tuned scale. +#' @return (Numeric scalar) +#' The chord's harmonicity, defined as the proportion of harmonics +#' of the chord's fundamental frequency that coincide with the +#' harmonics of the chord tones. +#' @references +#' \insertRef{Bowling2018}{incon} +#' @examples +#' gill09_harmonicity(c(60, 64, 67)) +#' gill09_harmonicity("60 64 67") +#' gill09_harmonicity(hrep::pi_chord(c(60, 64, 67))) +#' @export +gill09_harmonicity <- function(x, tonic = 0L) { + checkmate::qassert(tonic, "X1") + x <- hrep::pi_chord(x) + chord <- rationalise_chord(x, tonic) + fundamental <- gcd(chord) + max_freq <- lcm(chord) + chord_harmonics <- expand_harmonics(chord, max_freq) + fundamental_harmonics <- expand_harmonics(fundamental, max_freq) + mean(fundamental_harmonics %in% chord_harmonics) +} + +#' Minimum frequency distance +#' +#' This function returns the minimum distance between +#' the fundamental frequencies of a chord, +#' after Bowling et al. (2018). +#' It makes no assumptions about the chord's tuning. +#' @param x Chord to analyse. +#' The default method assumes that the chord is expressed +#' as a numeric vector of frequencies. +#' Representations from the hrep package +#' (e.g. \code{\link[hrep]{pi_chord}()}) +#' can be used to analyse chords expressed as MIDI note numbers. +#' @return (Numeric scalar) +#' The minimum distance between the fundamental frequencies of the chord, +#' in Hz. +#' @references +#' \insertRef{Bowling2018}{incon} +#' @rdname bowl18_min_freq_dist +#' @examples +#' bowl18_min_freq_dist(c(220, 440, 560)) # processed as frequencies +#' bowl18_min_freq_dist(hrep::fr_chord(c(220, 440, 560))) # same as above +#' bowl18_min_freq_dist(hrep::pi_chord(c(60, 64, 67))) # MIDI note numbers +#' @export +bowl18_min_freq_dist <- function(x) { + UseMethod("bowl18_min_freq_dist") +} + +#' @rdname bowl18_min_freq_dist +#' @export +bowl18_min_freq_dist.default <- function(x) { + bowl18_min_freq_dist(hrep::fr_chord(x)) +} + +#' @rdname bowl18_min_freq_dist +#' @export +bowl18_min_freq_dist.fr_chord <- function(x) { + if (length(x) < 2L) + as.numeric(NA) else + min(diff(as.numeric(x))) +} + +expand_harmonics <- function(x, max_freq) { + UseMethod("expand_harmonics") +} + +#' @export +expand_harmonics.rational_chord <- function(x, max_freq) { + purrr::map(seq_len(ncol(x)), + ~ expand_harmonics(fraction(x[, .]), max_freq)) %>% + Reduce(union, .) +} + +#' @export +expand_harmonics.fraction <- function(x, max_freq) { + stopifnot(is(max_freq, "fraction")) + n <- 0L + res <- new.env() + while (TRUE) { + n <- n + 1L + harmonic <- phonTools::reduce.fraction(x * c(n, 1L)) + if ((harmonic[1] / harmonic[2]) > + (max_freq[1] / max_freq[2])) break + res[[as.character(n)]] <- as.character(harmonic) + } + res <- as.character(as.list(res)) + names(res) <- NULL + res +} + +fraction <- function(x, ...) { + UseMethod("fraction") +} + +#' @export +fraction.numeric <- function(x, ...) { + checkmate::qassert(x, "X2") + class(x) <- "fraction" + x +} + +#' @export +as.character.fraction <- function(x, ...) { + paste0(x[1], "/", x[2]) +} + +half <- function(x) { + UseMethod("half") +} + +#' @export +half.fraction <- function(x) { + if (x[1] %% 2L == 0L) x[1] <- x[1] / 2L else x[2] <- x[2] * 2L + x +} + +double <- function(x) { + UseMethod("double") +} + +#' @export +double.fraction <- function(x) { + if (x[2] %% 2L == 0L) x[2] <- x[2] / 2L else x[1] <- x[1] * 2L + x +} + +gcd <- function(x) { + UseMethod("gcd") +} + +#' @export +gcd.rational_chord <- function(x) { + y <- c(numbers::mGCD(x[1, ]), + numbers::mLCM(x[2, ])) + fraction(y) +} + +lcm <- function(x) { + UseMethod("lcm") +} + +#' @export +lcm.rational_chord <- function(x) { + y <- c(numbers::mLCM(x[1, ]), + numbers::mGCD(x[2, ])) + fraction(y) +} + +# These functions assume that we are in the key of C +# (i.e. pitch-class 0 is the tonic). + +rational_chord <- function(x) { + stopifnot(is.matrix(x), is.numeric(x), nrow(x) == 2L) + class(x) <- "rational_chord" + x +} + +rationalise_chord <- function(x, tonic) { + UseMethod("rationalise_chord") +} + +#' @export +rationalise_chord.pi_chord <- function(x, tonic) { + x <- hrep::tp(x, - tonic) + octave <- floor(hrep::get_bass_pi(x) / 12) + x <- hrep::tp(x, - 12 * octave) + sapply(x, rationalise_pitch) %>% rational_chord +} + +rationalise_pitch <- function(x) { + checkmate::qassert(x, "X1") + octave <- floor(x / 12) + pitch_class <- x %% 12 + fraction <- rationalise_pitch_class(pitch_class) + while (octave != 0) { + if (octave < 0) { + fraction <- half(fraction) + octave <- octave + 1L + } else if (octave > 0) { + fraction <- double(fraction) + octave <- octave - 1L + } + } + fraction +} + +rationalise_pitch_class <- function(pc) { + checkmate::qassert(pc, "X1[0,12)") + fraction(rational_scale[, pc + 1L]) +} + +#' Rational scale +#' +#' This defines the rational scale used when computing harmonicity with +#' \code{\link{gill09_harmonicity}}. +#' It is a matrix with 2 rows and 12 columns, +#' where the first row corresponds to fraction numerators, +#' and the second row corresponds to fraction denominators. +#' Column i identifes the interval of size (i - 1) semitones. +#' For example, column 8 identifies the perfect fifth +#' (7 semitones) as a 3:2 ratio. +#' +#' @docType data +#' @keywords data +rational_scale <- matrix( + c(1, 16, 9, 6, 5, 4, 7, 3, 8, 5, 9, 15, + 1, 15, 8, 5, 4, 3, 5, 2, 5, 3, 5, 8), + nrow = 2, + byrow = TRUE +) diff --git a/R/model-corpdiss.R b/R/model-corpdiss.R new file mode 100644 index 0000000..c7b3d89 --- /dev/null +++ b/R/model-corpdiss.R @@ -0,0 +1,151 @@ +#' Consonance tables for popular music +#' +#' This table summarises chord prevalences in the +#' McGill Billboard corpus \insertCite{Burgoyne2011}{hcorp}. +#' These pieces were sampled from the Billboard magazine's +#' United States "Hot 100" chart between 1958 and 1991, +#' and transcribed by expert musicians. +#' See \code{\link[hcorp]{popular_1}} for more details. +#' +#' @details +#' Chords are represented as pitch-class chord types: +#' see \code{\link[hrep]{pc_chord_type}} for details. +#' +#' @name popular_1_pc_chord_type +#' @docType data +#' @references +#' \insertAllCited{} +#' @keywords data +NULL + +#' Corpus dissonance table +#' +#' Derives sufficient information from a corpus to formulate +#' a corpus-based dissonance model. +#' @param x Corpus to analyse, as created by \code{hrep::\link[hrep]{corpus}}. +#' @param type (Scalar character) Representation to which chords should be coerced +#' before counting. +#' @param add (Scalar numeric) Number to add to each count before computing probabilities. +#' This is useful for ensuring that chord probabilities exceed zero. +#' @return Returns an object of class \code{corpus_dissonance_table}, +#' a \link[tibble]{tibble} where each row corresponds to a +#' different pitch-class chord type (i.e. an object of class \code{pc_chord_type}), +#' with the mapping between integer indices and chord types defined by +#' the \code{hrep} package. +#' This tibble contains the following columns: +#' \item{count}{The number of times the chord type was observed in the corpus.} +#' \item{prob}{The inferred probability of that chord type.} +#' \item{log_prob}{The inferred log probability of that chord type.} +#' @rdname corpus_dissonance_table +#' @export +corpus_dissonance_table <- function(x, type = "pc_chord_type", add = 1L) { + UseMethod("corpus_dissonance_table") +} + +#' @rdname corpus_dissonance_table +#' @export +corpus_dissonance_table.corpus <- function(x, type = "pc_chord_type", add = 1L) { + df <- tibble::tibble(count = count_chords(x, type)) + df$count <- df$count + df$prob <- (df$count + add) / sum(df$count + add) + df$neg_log_prob <- - log(df$prob) + .corpus_dissonance_table(df, type) +} + +.corpus_dissonance_table <- function(df, type) { + class(df) <- c("corpus_dissonance_table", class(df)) + attr(df, "type") <- type + df +} + +#' Get type +#' +#' Gets the type of an object. +#' @param x Object. +#' @return (Character scalar) Type. +#' @export +type <- function(x) { + UseMethod("type") +} + +#' @export +print.corpus_dissonance_table <- function(x, ...) { + cat("# An object of class 'corpus_dissonance_table'\n") + cat("# Type: ", type(x), "\n", sep = "") + NextMethod() +} + +#' @export +type.corpus_dissonance_table <- function(x) { + attr(x, "type") +} + +`type<-.corpus_dissonance_table` <- function(x, value) { + attr(x, "type") <- value + x +} + +#' Corpus dissonance +#' +#' Calculates a corpus-based estimate of the dissonance of a sonority. +#' @details +#' By default, dissonance is estimated from chord prevalences +#' in the McGill Billboard dataset \insertCite{Burgoyne2011}{hcorp}. +#' The dataset's contents were sampled from the Billboard magazine's +#' United States "Hot 100" chart between 1958 and 1991, +#' and transcribed by expert musicians. +#' See \code{\link[hcorp]{popular_1}} for more details. +#' +#' By default, +#' the dissonance estimation treats chords as transposition invariant, +#' and chord pitches as octave-invariant, +#' but differentiates between different inversions of the same chord. +#' Different behaviour can be achieved by passing a custom corpus analysis +#' to the \code{table} argument. +#' @param x Sonority to analyse. +#' This should be an object created by the \code{hrep} package, +#' representing a pitch chord (\code{\link[hrep]{pi_chord}}), +#' representing a pitch-class chord (\code{\link[hrep]{pc_chord}}), +#' representing a pitch-class chord type (\code{\link[hrep]{pc_chord_type}}), +#' or a pitch-class set (\code{\link[hrep]{pc_set}}). +#' This object will be coerced to the same type as the corpus dissonance table, +#' i.e. \code{type(table)}. +#' @param table Corpus dissonance table, as created by +#' \code{\link{corpus_dissonance_table}()}. +#' This table summarises chord prevalences within a corpus. +#' The default is \code{\link{popular_1_pc_chord_type}}. +#' @return Dissonance estimate, as a numeric scalar. +#' @references +#' \insertAllCited{} +#' @export +corpus_dissonance <- function(x, table = incon::popular_1_pc_chord_type) { + typ <- type(table) + x <- hrep::represent(x, typ) + i <- hrep::encode(x) + table$neg_log_prob[i] +} + +#' Count chords +#' +#' This function counts chords within a corpus. +#' @param x Corpus to analyse. +#' @param type Representation to which chords should be coerced +#' before counting. +#' @return Integer vector providing the observed counts for each chord, +#' indexed by the type encoding defined in the \code{hrep} package. +#' @rdname count_chords +#' @export +count_chords <- function(x, type = "pc_chord_type") { + UseMethod("count_chords") +} + +#' @rdname count_chords +#' @export +count_chords.corpus <- function(x, type = "pc_chord_type") { + hrep::represent(x, type) %>% + do.call(c, .) %>% + factor(levels = seq_len(hrep::alphabet_size(type))) %>% + table %>% + as.integer +} + diff --git a/R/model-dycon.R b/R/model-dycon.R new file mode 100644 index 0000000..685c600 --- /dev/null +++ b/R/model-dycon.R @@ -0,0 +1,481 @@ +#' Spectral roughness (Hutchinson & Knopoff) +#' +#' Gets the roughness of a sonority according to the model of +#' \insertCite{Hutchinson1978;textual}{incon}. +#' @param x Object to analyse, which is coerced to the class +#' \code{\link[hrep]{sparse_fr_spectrum}}. +#' * Numeric vectors will be treated as vectors of MIDI note numbers, +#' and expanded into their implied harmonics. +#' * Two-element lists will be treated as finalised spectra, +#' with the first element being a numeric vector of frequencies, +#' and the second element being a numeric vector of amplitudes. +#' +#' @param a Parameter passed to \code{\link{hutch_g}()}. +#' +#' @param b Parameter passed to \code{\link{hutch_g}()}. +#' +#' @param cbw_cut_off Parameter passed to \code{\link{hutch_g}()}. +#' +#' @param dissonance_function +#' Function for computing dissonance contribution as a function of +#' critical bandwidth distance, defaulting to \code{\link{hutch_dissonance_function}}. +#' Custom functions may be specified here as long as they use the same parameter list +#' as the original function. +#' +#' @return Numeric scalar, identifying the roughness of the spectrum. +#' +#' @references +#' \insertAllCited{} +#' +#' @rdname roughness_hutch +#' +#' @md +#' +#' @export +roughness_hutch <- function( + x, + cbw_cut_off = 1.2, + a = 0.25, + b = 2, + dissonance_function = hutch_dissonance_function, + ...) { + UseMethod("roughness_hutch") +} + +#' @param ... Further arguments to pass to \code{\link[hrep]{sparse_fr_spectrum}}. +#' @rdname roughness_hutch +#' @export +roughness_hutch.default <- function( + x, + cbw_cut_off = 1.2, + a = 0.25, + b = 2, + dissonance_function = hutch_dissonance_function, + ... +) { + x <- hrep::sparse_fr_spectrum(x, ...) + roughness_hutch(x, cbw_cut_off = cbw_cut_off, a = a, b = b, dissonance_function = dissonance_function) +} + +#' @rdname roughness_hutch +#' @export +roughness_hutch.sparse_fr_spectrum <- function( + x, + cbw_cut_off = 1.2, + a = 0.25, + b = 2, + dissonance_function = hutch_dissonance_function, + ... +) { + frequency <- hrep::freq(x) + amplitude <- hrep::amp(x) + n <- length(frequency) + if (n < 2) 0 else { + # Compute denominator + denominator <- sum(amplitude ^ 2) + # Compute numerator + df <- expand.grid(j = seq_len(n), i = seq_len(n)) %>% + (function(df) { + df[df$i < df$j, ] + }) + df$a_i_a_j <- amplitude[df$i] * amplitude[df$j] + df$g_ij <- dissonance_function( + f1 = frequency[df$i], + f2 = frequency[df$j], + cbw_cut_off = cbw_cut_off, + a = a, + b = b + ) + numerator <- sum(df$a_i_a_j * df$g_ij) + numerator / denominator + } +} + +#' Get dissonance contribution +#' +#' Computes the dissonance contribution of a pair of pure tones. +#' +#' @inheritParams roughness_hutch +#' @inheritParams hutch_cbw +#' +#' @return Numeric vector of dissonance contributions. +#' +#' @export +hutch_dissonance_function <- function(f1, f2, cbw_cut_off = 1.2, a = 0.25, b = 2) { + hutch_g( + y = hutch_y(f1 = f1, f2 = f2), + cbw_cut_off = cbw_cut_off, + a = a, + b = b + ) +} + +#' Critical bandwidth (Hutchison & Knopoff) +#' +#' Calculates the critical bandwidth given pairs of frequencies +#' \code{f1} and \code{f2}, +#' according to the model of \insertCite{Hutchinson1978;textual}{incon}. +#' @param f1 (Numeric vector) Frequency 1, Hz +#' @param f2 (Numeric vector) Frequency 2, Hz +#' @return (Numeric vector) Critical bandwidths. +#' @references +#' \insertAllCited{} +#' @export +hutch_cbw <- function(f1, f2) { + mean_f <- (f1 + f2) / 2 + 1.72 * (mean_f ^ 0.65) +} + +#' Critical bandwidth distance (Hutchison & Knopoff) +#' +#' Calculates the distance between pairs of frequencies in units of +#' critical bandwidths, according to the model of +#' \insertCite{Hutchinson1978;textual}{incon}. +#' @param f1 (Numeric vector) Frequency 1, Hz +#' @param f2 (Numeric vector) Frequency 2, Hz +#' @return (Numeric vector) Unsigned distances in frequency bandwidths. +#' @references +#' \insertAllCited{} +#' @export +hutch_y <- function(f1, f2) { + abs_freq_diff <- abs(f1 - f2) + critical_bandwidth <- hutch_cbw(f1, f2) + abs_freq_diff / critical_bandwidth +} + +#' Dissonance factor (Hutchinson & Knopoff) +#' +#' Computes dissonance factors given frequency distances in units of +#' critical bandwidths, after Mashinter's implementation of +#' Hutchinson & Knopoff's model +#' \insertCite{Mashinter2006,Hutchinson1978}{incon}. +#' This function corresponds to an approximation of the +#' look-up table in \insertCite{Plomp1965;textual}{incon}. +#' @param y (Numeric vector) Frequency distance in units of critical bandwidths. +#' @param cbw_cut_off (Numeric scalar) +#' If not \code{NULL}, then should be a number +#' corresponding to the variable CBWcutoff in Mashinter's own implementation. +#' If \code{y >= cbw_cut_off}, then the dissonance factor will be approximated as 0. +#' Setting \code{cbw_cut_off} to 1.2 is necessary for replicating Mashinter's results. +#' A cut-off of 1.2 was also used by \insertCite{Bigand1996;textual}{incon}. +#' @param a (Numeric scalar, default = 0.25) +#' Parameter from \insertCite{Mashinter2006;textual}{incon}. +#' @param b (Numeric scalar, default = 2) +#' Parameter from \insertCite{Mashinter2006;textual}{incon}. +#' @return (Numeric vector) Dissonance factors. +#' @references +#' \insertAllCited{} +#' @export +hutch_g <- function(y, cbw_cut_off = 1.2, a = 0.25, b = 2) { + assertthat::assert_that( + is.numeric(y), all(y >= 0), + is.null(cbw_cut_off) || (assertthat::is.scalar(cbw_cut_off) && is.numeric(cbw_cut_off)), + is.numeric(a), is.numeric(b), + assertthat::is.scalar(a), assertthat::is.scalar(b) + ) + res <- ((y / a) * exp(1 - (y / a))) ^ b + if (!is.null(cbw_cut_off)) { + res[y > cbw_cut_off] <- 0 + } + res +} + +hutch_visualise_data <- function(x, cbw_cut_off, a, b, min_freq, max_freq, ...) { + frequency <- c(min_freq, hrep::freq(x), max_freq) + amplitude <- c(0, hrep::amp(x), 0) + n <- length(frequency) + df <- expand.grid(j = seq_len(n), i = seq_len(n)) %>% + tibble::as_tibble() %>% + dplyr::mutate( + a_i_a_j = amplitude[.data$i] * amplitude[.data$j], + g_ij = hutch_g( + y = hutch_y(f1 = frequency[.data$i], + f2 = frequency[.data$j]), + cbw_cut_off = cbw_cut_off, + a = a, + b = b + )) %>% + dplyr::group_by(.data$i) %>% + dplyr::summarise(dissonance = sum(.data$g_ij)) + df2 <- tibble::tibble(i = seq_len(n), frequency, amplitude) %>% + dplyr::left_join(df, by = "i") %>% + dplyr::mutate(pitch = hrep::freq_to_midi(frequency)) + df3 <- data.frame(pitch = numeric(n * 5), + amplitude = numeric(n * 5), + dissonance = numeric(n * 5)) + for (i in seq_len(n)) { + I <- (i - 1L) * 5L + df3[I + 1:5, "pitch"] <- df2[i, "pitch"] + df3[I + 2:4, "dissonance"] <- df2[i, "dissonance"] + df3$amplitude[I + 3L] <- df2$amplitude[i] + } + df3 +} + +#' Visualise +#' +#' Creates a plot visualising computations for Hutchinson & Knopoff's model. +#' +#' @param x Passed to \code{\link{roughness_hutch}}. +#' @param cbw_cut_off Passed to \code{\link{roughness_hutch}}. +#' @param a Passed to \code{\link{roughness_hutch}}. +#' @param b Passed to \code{\link{roughness_hutch}}. +#' @param label (Character scalar) x-axis label. +#' @param amplitude_breaks Numeric vector of tick locations for the y-axis. +#' @param colour_limits Defines the limits of the roughness scale. +#' @param colour_low Colour to use for the lowest roughness. +#' @param colour_high Colour to use for the highest roughness. +#' @param theme \code{\link[ggplot2]{ggplot}} theme to use. +#' @param ... Passed to \code{\link[hrep]{sparse_fr_spectrum}}. +#' @export +hutch_visualise <- function(x, + cbw_cut_off = 1.2, + a = 0.25, + b = 2, + label = "Roughness", + amplitude_breaks = c(0, 1), + colour_limits = c(0, 3), + colour_low = "darkblue", + colour_high = "red", + ...) { + stopifnot(is.list(x), !is.null(names(x)), !anyDuplicated(names(x))) + x <- purrr::map(x, hrep::sparse_fr_spectrum, ...) + min_freq <- min(purrr::map_dbl(x, ~ min(hrep::freq(.)))) + max_freq <- max(purrr::map_dbl(x, ~ max(hrep::freq(.)))) + labels <- factor(names(x), levels = names(x)) + purrr::map(x, hutch_visualise_data, cbw_cut_off, a, b, min_freq, max_freq, ...) %>% + purrr::map2(labels, ~ dplyr::mutate(.x, label = .y)) %>% + dplyr::bind_rows() %>% + ggplot2::ggplot(ggplot2::aes_string(x = "pitch", + y = "amplitude", + colour = "dissonance")) + + ggplot2::geom_line() + + ggplot2::scale_x_continuous("Pitch (MIDI)") + + ggplot2::scale_y_continuous("Amplitude", breaks = amplitude_breaks) + + ggplot2::scale_colour_gradient(label, + low = colour_low, + high = colour_high, + limits = colour_limits) + + ggplot2::facet_wrap(~ label, ncol = 1) +} + +#' Spectral roughness (Sethares) +#' +#' Gets the roughness of a sonority according to the model of Sethares (1993). +#' By default, the algorithm is modified according to +#' \insertCite{Sethares2005;textual}{incon} and +#' \insertCite{Weisser2013;textual}{incon}: +#' roughness is proportional to the minimum amplitude of each pair of partials, +#' not the product of their amplitudes. +#' This behaviour can be disabled by setting \code{min_amplitude = FALSE}. +#' @param x Object to analyse, which is coerced to the class +#' \code{\link[hrep]{sparse_fr_spectrum}}. +#' * Numeric vectors will be treated as vectors of MIDI note numbers, +#' and expanded into their implied harmonics. +#' * Two-element lists will be treated as finalised spectra, +#' with the first element being a numeric vector of frequencies, +#' and the second element being a numeric vector of amplitudes. +#' @param min_amplitude See \code{\link{dyad_roughness_seth}}. +#' @return Estimated roughness, as a numeric scalar. +#' @note \insertCite{Sethares2005;textual}{incon} +#' suggests using loudnesses instead of amplitudes. +#' However, he acknowledges that loudness is difficult to calculate +#' for arbitrary timbres. +#' Furthermore, if we replace amplitude with roughness, +#' we lose the original model's invariance to multiplicative +#' scaling of the original signal. +#' In this implementation, we therefore stay with amplitude, +#' consistent with \insertCite{Sethares1993;textual}{incon}. +#' @references +#' \insertAllCited{} +#' @rdname roughness_seth +#' @md +#' @export +roughness_seth <- function(x, min_amplitude = TRUE, ...) { + UseMethod("roughness_seth") +} + +#' @param ... Further arguments to pass to \code{\link[hrep]{sparse_fr_spectrum}}. +#' @rdname roughness_seth +#' @export +roughness_seth.default <- function(x, min_amplitude = TRUE, ...) { + x <- hrep::sparse_fr_spectrum(x, ...) + roughness_seth(x, min_amplitude = min_amplitude) +} + +#' @rdname roughness_seth +#' @export +roughness_seth.sparse_fr_spectrum <- function(x, min_amplitude = TRUE, ...) { + frequency <- hrep::freq(x) + amplitude <- hrep::amp(x) + n <- length(frequency) + if (n < 2) 0 else { + # The formula given in Sethares (1993) iterates over all pairs of [i, j], + # but because roughness is symmetric we can save time by only considering pairs. + # This gets rid of the 'divide by two' component in the original equation. + df <- expand.grid(j = seq_len(n), i = seq_len(n)) %>% + (function(df) { + df[df$i < df$j, ] + }) + dyad_roughness_seth( + f1 = frequency[df$i], + f2 = frequency[df$j], + a1 = amplitude[df$i], + a2 = amplitude[df$j], + min_amplitude = min_amplitude + ) %>% sum + } +} + +#' Dyad roughness (Sethares) +#' +#' Gets the roughness of a dyad according to the model of Sethares (1993). +#' @param f1 Frequency of tone 1 (Hz) (numeric vector). +#' @param f2 Frequency of tone 2 (Hz) (numeric vector). Must be greater than \code{f1}. +#' @param a1 amplitude of tone 1 (numeric vector). +#' @param a2 amplitude of tone 2 (numeric vector). +#' @param ensure_f1_is_less_than_f2 If \code{FALSE}, assumes that \code{f1 < f2}. +#' @param min_amplitude If \code{TRUE}, +#' roughness is considered to be proportional to +#' the minimum amplitude of each pair of partials, +#' rather than the product of their amplitudes. +#' The default (\code{TRUE}) corresponds to the algorithm as updated by +#' \insertCite{Sethares2005;textual}{incon} and +#' \insertCite{Weisser2013;textual}{incon}. +#' Set to \code{FALSE} to recover the original algorithm from +#' \insertCite{Sethares1993;textual}{incon}. +#' @param a Numeric scalar parameter, optimised to 3.5 (default) in Sethares (1993). +#' @param b Numeric scalar parameter, optimised to 5.75 (default) in Sethares (1993). +#' @param s1 Numeric scalar parameter from Sethares (1993). +#' @param s2 Numeric scalar parameter from Sethares (1993). +#' @param d_star Numeric scalar parameter from Sethares (1993). +#' @return Numeric vector of roughnesses. +#' @references +#' \insertAllCited{} +dyad_roughness_seth <- function(f1, f2, a1, a2, + ensure_f1_is_less_than_f2 = TRUE, + min_amplitude = TRUE, + a = 3.5, + b = 5.75, + s1 = 0.021, + s2 = 19, + d_star = 0.24) { + assertthat::assert_that( + is.numeric(f1), is.numeric(f2), + is.numeric(a1), is.numeric(a2), + is.logical(min_amplitude), length(min_amplitude) == 1L, + length(f1) == length(f2), + length(f1) == length(a1), + length(f1) == length(a2) + ) + if (ensure_f1_is_less_than_f2) { + need_reversal <- f1 > f2 + dyad_roughness_seth( + f1 = ifelse(need_reversal, f2, f1), + f2 = ifelse(need_reversal, f1, f2), + a1 = ifelse(need_reversal, a2, a1), + a2 = ifelse(need_reversal, a1, a2), + ensure_f1_is_less_than_f2 = FALSE, + a = a, b = b, s1 = s1, s2 = s2, d_star = d_star + ) + } else { + s <- d_star / (s1 * f1 + s2) + A <- if (min_amplitude) pmin(a1, a2) else a1 * a2 + A * ( + exp(- a * s * (f2 - f1)) - + exp(- b * s * (f2 - f1)) + ) + } +} + +#' Spectral roughness (Vassilakis) +#' +#' Gets the roughness of a sonority according to the model of +#' \insertCite{Vassilakis2001;textual}{incon} +#' \insertCite{Villegas2010;textual}{incon} +#' @param x Object to analyse, which is coerced to the class +#' \code{\link[hrep]{sparse_fr_spectrum}}. +#' * Numeric vectors will be treated as vectors of MIDI note numbers, +#' and expanded into their implied harmonics. +#' * Two-element lists will be treated as finalised spectra, +#' with the first element being a numeric vector of frequencies, +#' and the second element being a numeric vector of amplitudes. +#' @return Estimated roughness, as a numeric scalar. +#' @references +#' \insertAllCited{} +#' @rdname roughness_vass +#' @md +#' @export +roughness_vass <- function(x, ...) { + UseMethod("roughness_vass") +} + +#' @param ... Further arguments to pass to \code{\link[hrep]{sparse_fr_spectrum}}. +#' @rdname roughness_vass +#' @export +roughness_vass.default <- function(x, ...) { + x <- hrep::sparse_fr_spectrum(x, ...) + roughness_vass(x) +} + +#' @rdname roughness_vass +#' @export +roughness_vass.sparse_fr_spectrum <- function(x, ...) { + tolerance <- 1e-5 + x <- x[x$y > tolerance, ] # eliminate partials with near-zero weight + frequency <- hrep::freq(x) + amplitude <- hrep::amp(x) + n <- length(frequency) + if (n < 2) 0 else { + # Roughness is computed by summing over all dyadic roughnesses. + # Noting that the formula for dyadic roughness is symmetric, + # we can instead only compute dyadic roughnesses for pairs + # where i < j, and then double the resulting sum. + df <- expand.grid(j = seq_len(n), i = seq_len(n)) %>% + (function(df) { + df[df$i < df$j, ] + }) + dyad_roughness_vass( + f1 = frequency[df$i], + f2 = frequency[df$j], + a1 = amplitude[df$i], + a2 = amplitude[df$j] + ) %>% sum %>% magrittr::multiply_by(2) + } +} + +#' Dyad roughness (Vassilakis) +#' +#' Gets the roughness of a dyad according to the model of +#' \insertCite{Vassilakis2001;textual}{incon} +#' \insertCite{Villegas2010;textual}{incon} +#' @param f1 Frequency of tone 1 (Hz) (numeric vector). +#' @param f2 Frequency of tone 2 (Hz) (numeric vector). +#' @param a1 amplitude of tone 1 (numeric vector). +#' @param a2 amplitude of tone 2 (numeric vector). +#' @return Numeric vector of roughnesses. +#' @note The function is vectorised over all inputs. +#' @references +#' \insertAllCited{} +dyad_roughness_vass <- function(f1, f2, a1, a2) { + assertthat::assert_that( + is.numeric(f1), is.numeric(f2), + is.numeric(a1), is.numeric(a2), + length(f1) == length(f2), + length(f1) == length(a1), + length(f1) == length(a2) + ) + ((a1 * a2) ^ 0.1) * + 0.5 * + (((2 * pmin(a1, a2)) / (a1 + a2)) ^ 3.11) * + (exp(- 3.5 * f_vass(f1, f2)) - exp(- 5.75 * f_vass(f1, f2))) +} + +f_vass <- function(f1, f2) { + s_vass(pmin(f1, f2)) * + abs(f1 - f2) +} + +s_vass <- function(f) { + 0.24 / (0.0207 * f + 18.96) +} diff --git a/R/har-2019.R b/R/model-har-2019.R similarity index 85% rename from R/har-2019.R rename to R/model-har-2019.R index 709a35c..a6941f6 100644 --- a/R/har-2019.R +++ b/R/model-har-2019.R @@ -22,21 +22,21 @@ har_19_composite_coef <- tibble::tribble( #' #' The model combines several sub-models: #' - `hutch_78_roughness`, -#' the roughness model of \insertCite{Hutchinson1978;textual}{dycon} -#' (see \code{dycon::\link[dycon]{roughness_hutch}}); +#' the roughness model of \insertCite{Hutchinson1978;textual}{incon} +#' (see \code{incon::\link[incon]{roughness_hutch}}); #' - `har_18_harmonicity`, -#' the harmonicity model of \insertCite{Harrison2018;textual}{har18} -#' (see \code{har18::\link[har18]{pc_harmonicity}}); +#' the harmonicity model of \insertCite{Harrison2018;textual}{incon} +#' (see \code{incon::\link[incon]{pc_harmonicity}}); #' - `har_19_corpus`: #' a corpus-based model of cultural familiarity #' (Harrison & Pearce, in preparation) -#' (see \code{corpdiss::\link[corpdiss]{corpus_dissonance}}). +#' (see \code{incon::\link[incon]{corpus_dissonance}}). #' #' This model uses the regression coefficients #' provided in \code{\link{har_19_composite_coef}}, with one caveat: #' by default, the chord size effect is disabled, #' because it's thought that this effect came from a confound -#' in the perceptual data of \insertCite{Bowling2018;textual}{bowl18}. +#' in the perceptual data of \insertCite{Bowling2018;textual}{incon}. #' #' @param x Chord to analyse; passed to \code{\link{incon}}. #' All chord pitches must be integer-valued. diff --git a/R/model-har18.R b/R/model-har18.R new file mode 100644 index 0000000..e1555c6 --- /dev/null +++ b/R/model-har18.R @@ -0,0 +1,264 @@ +#' Cosine similarity +#' +#' Computes the cosine similarity between two numeric vectors. +#' @param x Numeric vector 1. +#' @param y Numeric vector 2. +#' @return Cosine similarity, as a numeric scalar. +#' @export +cosine_similarity <- function(x, y) { + numerator <- sum(x * y) + denominator <- + sqrt(sum(x ^ 2)) * + sqrt(sum(y ^ 2)) + numerator / denominator +} + +#' Peak +#' +#' Gets the peak value of an object. +#' @param x Object to analyse. +#' @return The object's peak value, as a numeric scalar. +#' @rdname peak +#' @export +peak <- function(x) { + UseMethod("peak") +} + +#' @rdname peak +#' @export +peak.milne_pc_spectrum <- function(x) { + max(as.numeric(x)) +} + +#' Kullback-Leibler divergence from uniform +#' +#' Gets the Kullback-Leibler divergence of a provided distribution +#' from a uniform distribution. +#' @param x Input distribution. +#' @return The Kullback-Leibler divergence from a uniform distribution +#' to the input distribution. +#' @rdname kl_div_from_uniform +#' @export +kl_div_from_uniform <- function(x) { + UseMethod("kl_div_from_uniform") +} + +#' @rdname kl_div_from_uniform +#' @export +kl_div_from_uniform.smooth_spectrum <- function(x) { + # Construct a probability vector, where each bin corresponds to + # the probability of a discrete event + x <- as.numeric(x) + probs <- x / sum(x) + n <- length(probs) + uniform_probs <- 1 / n + non_zero_probs <- probs[probs > 0] + sum( + non_zero_probs * log(non_zero_probs / uniform_probs, base = 2) + ) +} + +#' Pitch-class harmonicity +#' +#' Gets the pitch-class harmonicity of an input sonority, after +#' \insertCite{Harrison2018;textual}{incon} and +#' \insertCite{Milne2013;textual}{incon}. +#' @param x Object to analyse. +#' @param method (Character scalar) Method to use. +#' * \code{"kl"} (default) delivers the Kullback-Leibler method of +#' \insertCite{Harrison2018;textual}{incon}. +#' * \code{"peak"} delivers the peak-value method of +#' \insertCite{Milne2013;textual}{incon}. +#' @param num_harmonics (Integerish scalar) +#' Number of harmonics to use when expanding tones into their implied harmonics, +#' and when defining the harmonic template +#' (including the fundamental frequency). +#' Defaults to 12, after +#' \insertCite{Milne2016;textual}{incon}. +#' @param rho (Numeric scalar) +#' Roll-off parameter for harmonic expansion. +#' Defaults to 0.75, after +#' \insertCite{Milne2016;textual}{incon}. +#' @param sigma (Numeric scalar) +#' Standard deviation of the Gaussian smoothing distribution (cents). +#' Defaults to 6.83, after +#' \insertCite{Milne2016;textual}{incon}. +#' @param array_dim (Integerish scalar) +#' Dimensionality of the pitch-class spectrum array. +#' Defaults to 1200, after +#' \insertCite{Milne2016;textual}{incon}. +#' @param ... Arguments passed to specific methods. +#' @return Pitch-class harmonicity, as a numeric scalar. +#' @note This algorithm makes use of \code{\link[hrep]{milne_pc_spectrum}()} +#' as defined in the \code{hrep} package. +#' @md +#' @references +#' \insertAllCited{} +#' @examples +#' pc_harmonicity(c(0, 4, 7)) +#' pc_harmonicity(c(0, 3, 7)) +#' pc_harmonicity(c(0, 3, 6)) +#' @rdname pc_harmonicity +#' @export +pc_harmonicity <- function(x, + method = "kl", + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + ...) { + UseMethod("pc_harmonicity") +} + +#' @rdname pc_harmonicity +#' @export +pc_harmonicity.default <- function(x, + method = "kl", + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + array_dim = 1200, + ...) { + x <- hrep::pc_set(x) + do.call(pc_harmonicity, as.list(environment())) +} + +#' @rdname pc_harmonicity +#' @export +pc_harmonicity.pc_set <- function(x, + method = "kl", + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + array_dim = 1200, + ...) { + checkmate::qassert(method, "S1") + + x <- hrep::milne_pc_spectrum(x, + num_harmonics = num_harmonics, + rho = rho, + sigma = sigma, + array_dim = array_dim) + pc_harmonicity(x, + method = method, + num_harmonics = num_harmonics, + rho = rho, + sigma = sigma) +} + +#' @rdname pc_harmonicity +#' @export +pc_harmonicity.milne_pc_spectrum <- function(x, + method = "kl", + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + ...) { + checkmate::qassert(method, "S1") + + y <- sweep_harmonic_template(x, + num_harmonics = num_harmonics, + rho = rho, + sigma = sigma) + if (method == "kl") { + kl_div_from_uniform(y) + } else if (method == "peak") { + peak(y) + } else stop("unrecognised method") +} + +#' Sweep harmonic template +#' +#' Sweeps a harmonic template over an input spectrum. +#' @param x Object to analyse. +#' @param num_harmonics See \code{\link{pc_harmonicity}}. +#' @param rho See \code{\link{pc_harmonicity}}. +#' @param sigma See \code{\link{pc_harmonicity}}. +#' @param array_dim See \code{\link{pc_harmonicity}}. +#' @param ... Arguments passed to specific methods. +#' @return An object of class \code{\link[hrep]{milne_pc_spectrum}}, +#' identifying each pitch class with a perceptual weight +#' corresponding to its harmonic template fit. +#' @rdname sweep_harmonic_template +#' @export +sweep_harmonic_template <- function(x, + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + array_dim = 1200, + ...) { + UseMethod("sweep_harmonic_template") +} + +#' @rdname sweep_harmonic_template +#' @export +sweep_harmonic_template.pc_set <- function(x, + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + array_dim = 1200, + ...) { + hrep::milne_pc_spectrum(x, + num_harmonics = num_harmonics, + rho = rho, + sigma = sigma, + array_dim = array_dim) %>% + sweep_harmonic_template(num_harmonics = num_harmonics, + rho = rho, + sigma = sigma) +} + +#' @rdname sweep_harmonic_template +#' @export +sweep_harmonic_template.milne_pc_spectrum <- function(x, + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + ...) { + x <- as.numeric(x) + array_dim <- length(x) + template <- hrep::milne_pc_spectrum(hrep::pc_set(0), + array_dim = array_dim, + num_harmonics = num_harmonics, + rho = rho, + sigma = sigma) + res <- sweep_template(x, template) + + hrep::.milne_pc_spectrum(res) +} + +#' Sweep template +#' +#' Sweeps a circular template over a circular vector +#' and computes the cosine similarity at each possible offset. +#' +#' @param x +#' (Numeric vector) +#' The vector to be swept over. +#' +#' @param template +#' (Numeric vector) +#' The template to sweep over \code{x}. +#' Should have the same dimensionality as \code{x}. +#' +#' @param legacy +#' (Logical scalar) +#' Whether to use the legacy R implementation +#' (default = \code{FALSE}). +#' Otherwise the faster C++ implementation is used. +#' +#' @export +sweep_template <- function(x, template, legacy = FALSE) { + if (!legacy) { + return(sweep_template_cpp(x, template)) + } + + array_dim <- length(x) + res <- numeric(array_dim) + + for (i in seq_len(array_dim)) { + indices <- 1 + (seq(from = i - 1, length.out = array_dim) %% array_dim) + res[i] <- cosine_similarity(template, x[indices]) + } + + res +} diff --git a/R/huron-1994.R b/R/model-huron-1994.R similarity index 55% rename from R/huron-1994.R rename to R/model-huron-1994.R index 24bdb69..b362a95 100644 --- a/R/huron-1994.R +++ b/R/model-huron-1994.R @@ -1,21 +1,36 @@ +#' Dyadic consonance (Huron 1994) +#' +#' Computes the aggregate dyadic consonance of a chord following +#' Huron (1994). +#' +#' @param x Chord to evaluate, will be coerced to a +#' pitch-class set (\code{\link[hrep]{pc_set}}). +#' @export huron_1994 <- function(x) { UseMethod("huron_1994") } +#' @export huron_1994.default <- function(x) { x <- hrep::pc_set(x) huron_1994.pc_set(x) } +#' @export huron_1994.pc_set <- function(x) { x <- hrep::int_vec(x) huron_1994.int_vec(x) } +#' @export huron_1994.int_vec <- function(x) { sum(x * huron_1994_weights) } +#' Dyadic consonance weights (Huron 1994) +#' +#' A vector of the weights used in Huron's (1994) dyadic consonance model. +#' @export huron_1994_weights <- c(- 1.428, - 0.582, 0.594, diff --git a/R/model-jl12.R b/R/model-jl12.R new file mode 100644 index 0000000..3973499 --- /dev/null +++ b/R/model-jl12.R @@ -0,0 +1,149 @@ +#' Tonal dissonance +#' +#' Computes tonal dissonance using the algorithm +#' of \insertCite{Johnson-Laird2012;textual}{incon}. +#' @param x Sonority to analyse. +#' This will be coerced to an object of class \code{\link[hrep]{pc_set}}. +#' @return Integer scalar identifying the chord's consonance rank, +#' with higher values corresponding to increasing degrees of dissonance. +#' @references +#' \insertAllCited{} +#' @rdname jl_tonal_dissonance +#' @examples +#' jl_tonal_dissonance(c(0, 4, 7)) +#' jl_tonal_dissonance(c(0, 3, 7)) +#' jl_tonal_dissonance(c(0, 3, 6)) +#' @export +jl_tonal_dissonance <- function(x) { + UseMethod("jl_tonal_dissonance") +} + +#' @rdname jl_tonal_dissonance +#' @export +jl_tonal_dissonance.default <- function(x) { + x <- hrep::pc_set(x) + jl_tonal_dissonance(x) +} + +#' @rdname jl_tonal_dissonance +#' @export +jl_tonal_dissonance.pc_set <- function(x) { + y <- jl_rule_1(x) * 4L + + jl_rule_2(x) * 2L + + jl_rule_3(x) - 3L + attributes(y) <- NULL + y +} + +#' Tonal Dissonance, Rule 1 +#' +#' "Chords occurring in a major scale should be less dissonant +#' than chords occurring only in a minor scale, +#' which in turn should be less dissonant than chords +#' occurring in neither sort of scale." +#' \insertCite{Johnson-Laird2012}{incon}. +#' @param pc_set (Numeric vector) Pitch-class set to analyse. +#' No input checking is performed. +#' @return 1, 2, or 3, corresponding to increasing degrees of dissonance. +#' @references +#' \insertAllCited{} +#' @export +jl_rule_1 <- function(pc_set) { + if (in_major_scale(pc_set)) 1L else + if (in_minor_scale(pc_set)) 2L else + 3L +} + +#' Tonal Dissonance, Rule 2 +#' +#' "Chords that are consistent with a major triad are more consonant +#' than chords that are not consistent with a major triad" +#' \insertCite{Johnson-Laird2012}{incon}. +#' Consistency with the major triad means that a major triad +#' must be contained within the chord. +#' Additionally, all notes must be contained within a major scale. +#' @param pc_set (Numeric vector) Pitch-class set to analyse. +#' No input checking is performed. +#' @return \code{TRUE} for dissonant, \code{FALSE} for consonant. +#' @references +#' \insertAllCited{} +#' @export +jl_rule_2 <- function(pc_set) { + pc_set <- as.numeric(pc_set) + for (root in 0:11) { + triad <- sort((c(0, 4, 7) + root) %% 12) + if (all(triad %in% pc_set) && + in_major_scale(pc_set)) return(FALSE) + } + TRUE +} + +#' Tonal Dissonance, Rule 3 +#' +#' "Chords built from intervals of a third should be more consonant +#' than chords that are not built from thirds." +#' "The principle allows for just one missing third intervening +#' between two pitch classes a fifth apart" +#' \insertCite{Johnson-Laird2012}{incon}. +#' @param pc_set (Numeric vector) Pitch-class set to analyse. +#' @return \code{FALSE} for consonant, \code{TRUE} for dissonant. +#' If a consonant solution is found, +#' the stacked-thirds version of the chord is returned +#' as the attribute \code{"solution"}, +#' accessible with the command \code{attr(x, "solution")}. +#' @references +#' \insertAllCited{} +#' @export +jl_rule_3 <- function(pc_set) { + N <- length(pc_set) + pc_set <- as.numeric(pc_set) + # These contain all the different possible intervallic structures + # for creating a pitch-class set the same size as the + # current pitch-class set by stacking thirds, and + # allowing one pitch class to be missing. + # Solutions with no pitch class missing correspond to + # subsets of these vectors. + perm <- gtools::permutations(n = 2L, r = N, + v = c(3L, 4L), + repeats.allowed = TRUE) + # Iterate over every pitch class and try and + # build the chord by stacking thirds on top of + # that pitch class + for (i in seq_along(pc_set)) { + base <- pc_set[i] + for (j in seq_len(nrow(perm))) { + res <- (base + c(0L, cumsum(perm[j, ]))) %% 12L + if (all(pc_set %in% res)) { + out <- FALSE + attr(out, "solution") <- res + return(out) + } + } + } + TRUE +} + +in_major_scale <- function(pc_set) { + for (scale in major_scales) { + if (all(as.numeric(pc_set) %in% scale)) return(TRUE) + } + FALSE +} +in_minor_scale <- function(pc_set) { + for (scale in minor_scales) { + if (all(as.numeric(pc_set) %in% scale)) return(TRUE) + } + FALSE +} + +c_major_scale <- c(0, 2, 4, 5, 7, 9, 11) +c_minor_scale <- c(0, 2, 3, 5, 7, 8, 11) + +major_scales <- lapply(0:11, function(x) { + sort((c_major_scale + x) %% 12) +}) +minor_scales <- lapply(0:11, function(x) { + sort((c_minor_scale + x) %% 12) +}) +names(major_scales) <- 0:11 +names(minor_scales) <- 0:11 diff --git a/R/model-parn88.R b/R/model-parn88.R new file mode 100644 index 0000000..d6a9a40 --- /dev/null +++ b/R/model-parn88.R @@ -0,0 +1,159 @@ +#' Root by pitch-class chord +#' +#' This vector stores precomputed chord roots for +#' all pitch-class chords, +#' indexed by their encoding as defined in the "hrep" package. +#' See \code{\link[hrep]{pc_chord}} for more details. +#' +#' @name root_by_pc_chord +#' @docType data +#' @keywords data +NULL + +#' Parncutt (1988) +#' +#' Analyses a pitch-class set using the root-finding model of +#' \insertCite{Parncutt1988;textual}{incon}. +#' @param x Sonority to analyse. +#' This will be coerced to an object of class \code{\link[hrep]{pc_set}}. +#' @param root_support (Character scalar or data frame) +#' Identifies the root support weights to use. +#' * \code{"v2"} (default) uses the updated +#' weights from \insertCite{Parncutt2006;textual}{incon}. +#' * \code{"v1"} uses the original weights from \insertCite{Parncutt2006;textual}{incon}. +#' +#' See \code{\link{root_support_weights}} for the values of these weights. +#' Alternatively, root-support weights can be provided as a data frame, +#' with one column (interval) identifying the ascending interval in semitones, +#' and another column (weight) identifying the corresponding root support weight. +#' @param exponent (Numeric scalar) Exponent to be used when computing +#' root ambiguities. Defaults to 0.5, after \insertCite{Parncutt1988;textual}{incon}. +#' @return A list with three values: +#' * \code{root}, the estimated chord root (integer scalar); +#' * \code{root_ambiguity}, the root ambiguity (numeric scalar), +#' * \code{pc_weight}, a 12-dimensional vector of weights by pitch class. +#' @references +#' \insertAllCited{} +#' @md +#' @rdname parn88 +#' @export +parn88 <- function(x, root_support = "v2", exponent = 0.5) { + UseMethod("parn88") +} + +#' @rdname parn88 +#' @export +parn88.default <- function(x, root_support = "v2", exponent = 0.5) { + x <- hrep::pc_set(x) + do.call(parn88, args = as.list(environment())) +} + +#' @rdname parn88 +#' @export +parn88.pc_set <- function(x, root_support = "v2", exponent = 0.5) { + root_support <- get_root_support_weights(root_support) + + checkmate::qassert(x, "X+[0,11]") + checkmate::qassert(exponent, "R1") + checkmate::qassert(root_support, "R12") + stopifnot(!anyDuplicated(x)) + + w <- purrr::map_dbl(0:11, + pc_weight, + pc_set = encode_pc_set(x), + root_support = root_support) + + list(root = which.max(w) - 1L, + root_ambiguity = get_root_ambiguity(w, exponent = exponent), + pc_weight = w) +} + +#' Root +#' +#' Estimates the chord root of a pitch-class set using the root-finding model of +#' \insertCite{Parncutt1988;textual}{incon}. +#' This function is a wrapper for \code{\link{incon}}. +#' @param ... Arguments to pass to \code{\link{incon}}. +#' @return The estimated chord root (integer scalar). +#' @references +#' \insertAllCited{} +#' @export +root <- function(...) { + parn88(...)$root +} + +#' Root ambiguity +#' +#' Estimates the root ambiguity of a pitch-class set using the root-finding model of +#' \insertCite{Parncutt1988;textual}{incon}. +#' This function is a wrapper for \code{\link{parn88}}. +#' @param ... Arguments to pass to \code{\link{parn88}}. +#' @return The root ambiguity (numeric scalar). +#' @references +#' \insertAllCited{} +#' @export +root_ambiguity <- function(...) { + parn88(...)$root_ambiguity +} + +#' Root support weights +#' +#' A list of different root support weights that may be used +#' by the root-finding algorithm of \insertCite{Parncutt1988;textual}{incon}. +#' See \code{\link{parn88}} for more information. +#' @references +#' \insertAllCited{} +#' @export +root_support_weights <- list( + v1 = tibble::tribble( + ~ interval, ~ weight, + 0, 1, + 7, 1/2, + 4, 1/3, + 10, 1/4, + 2, 1/5, + 3, 1/10 + ), + v2 = tibble::tribble( + ~ interval, ~ weight, + 0, 10, + 7, 5, + 4, 3, + 10, 2, + 2, 1 + ) +) %>% purrr::map(function(df) { + x <- numeric(12) + x[df$interval + 1] <- df$weight + x +}) + +get_root_ambiguity <- function(x, exponent) { + checkmate::qassert(x, "R12") + checkmate::qassert(exponent, "R1") + x_max <- max(x) + sum(x / x_max) ^ exponent +} + +pc_weight <- function(pc, pc_set, root_support) { + checkmate::qassert(pc, "X1") + checkmate::qassert(pc_set, "X12[0,1]") + checkmate::qassert(root_support, "R12") + ind <- (seq(from = pc, length.out = 12L) %% 12L) + 1L + sum(pc_set[ind] * root_support) +} + +encode_pc_set <- function(x) { + checkmate::qassert(x, "X[0,11]") + y <- integer(12) + y[x + 1] <- 1L + y +} + +get_root_support_weights <- function(root_support) { + if (is.character(root_support)) { + stopifnot(length(root_support) == 1L, + root_support %in% names(root_support_weights)) + root_support <- root_support_weights[[root_support]] + } +} diff --git a/R/model-parn94.R b/R/model-parn94.R new file mode 100644 index 0000000..c1dd13c --- /dev/null +++ b/R/model-parn94.R @@ -0,0 +1,621 @@ +add_combined_spectrum <- function(x, par) { + df <- merge_spectra(x$pure_spectrum, x$complex_spectrum) + df$combined_audibility <- pmax(df$pure_tone_audibility, + df$complex_tone_audibility, + 0, na.rm = TRUE) + df$salience <- get_tone_salience(df$combined_audibility, par$k_s) + x$combined_spectrum <- df[, c("pitch", "combined_audibility", "salience")] + x +} + +merge_spectra <- function(pure_spectrum, complex_spectrum) { + merge(pure_spectrum[, c("pitch", "pure_tone_audibility")], + complex_spectrum[, c("pitch", "complex_tone_audibility")], + by = "pitch", + all = TRUE) +} +add_complex_spectrum <- function(x, par) { + spectrum <- x$pure_spectrum[, c("pitch", "pure_tone_audibility")] + template <- get_template(par) + df <- tibble::tibble( + pitch = seq(from = par$min_midi, + to = par$max_midi), + complex_tone_audibility = purrr::map_dbl(.data$pitch, + template_match, + template, + spectrum, + par) + ) + x$complex_spectrum <- df[df$complex_tone_audibility > 0, ] + x +} + +template_match <- function(fundamental, template, spectrum, par) { + transposed_template <- tibble::tibble(pitch = template$interval + fundamental, + weight = template$weight) + df <- merge(transposed_template, spectrum, + all.x = FALSE, all.y = FALSE) + ((sum(sqrt(df$weight * df$pure_tone_audibility))) ^ 2) / par$k_t +} + +get_template <- function(par) { + hrep::pi_chord(0) %>% + {hrep::sparse_pi_spectrum(., + num_harmonics = par$template_num_harmonics, + roll_off = par$template_roll_off, + digits = 0)} %>% + (tibble::as_tibble) %>% + {magrittr::set_names(., c("interval", "weight"))} +} +# @param res Where results are stored +# @param x Input spectrum +add_pure_spectrum <- function(res, x, par) { + y <- tibble::tibble( + pitch = hrep::pitch(x), + amplitude = hrep::amp(x), + kHz = hrep::midi_to_freq(.data$pitch) / 1000, + level = hrep::amplitude_to_dB(.data$amplitude, par$unit_amplitude_in_dB), + free_field_threshold = get_free_field_threshold(.data$kHz), + auditory_level = pmax(.data$level - .data$free_field_threshold, 0), + pure_tone_height = get_pure_tone_height(.data$kHz), + overall_masking_level = get_overall_masking_level(.data$auditory_level, + .data$pure_tone_height, + k_m = par$k_m), + pure_tone_audible_level = get_pure_tone_audible_level(.data$auditory_level, + .data$overall_masking_level), + pure_tone_audibility = get_pure_tone_audibility(.data$pure_tone_audible_level, + al_0 = par$al_0) + ) + res$pure_spectrum <- y[y$pure_tone_audibility > 0, ] + res +} +#' Get complex sonorousness +#' +#' Computes the complex sonorousness of a sound, after +#' \insertCite{Parncutt1994;textual}{incon}. +#' @param x Object to analyse. +#' @param k_c Parncutt & Strasburger (1994) set this to 0.2 (p. 105) +#' @param ... Further parameters to pass to \code{\link{parn94}()}. +#' @return Complex sonorousness, a numeric scalar. +#' @rdname complex_sonor +#' @references +#' \insertAllCited{} +#' @export +complex_sonor <- function(x, k_c = parn94_params()$k_c, ...) { + UseMethod("complex_sonor") +} + +#' @rdname complex_sonor +#' @export +complex_sonor.parn94 <- function(x, k_c = parn94_params()$k_c, ...) { + audibility <- x$complex_spectrum$complex_tone_audibility + if (length(audibility) == 0) + 0 else + k_c * max(audibility) +} + +#' @rdname complex_sonor +#' @export +complex_sonor.default <- function(x, k_c = parn94_params()$k_c, ...) { + x <- parn94(x, ...) + complex_sonor(x, k_c = k_c) +} +#' Get free field threshold +#' +#' Returns the free-field threshold (dB SPL) of hearing in quiet for pure tones +#' of given frequencies. +#' This is the minimum sound level at which a pure tone at that frequency +#' will be heard. +#' Corresponds to Equation 2 in \insertCite{Parncutt1994;textual}{incon}. +#' @param kHz Numeric vector of frequencies in kHz. +#' @return Numeric vector of corresponding free-field thresholds in dB SPL. +#' @references +#' \insertAllCited{} +#' @export +get_free_field_threshold <- function(kHz) { + 3.64 * (kHz ^ -0.8) - + 6.5 * exp(- 0.6 * (kHz - 3.3) ^ 2) + + (10 ^ (-3)) * (kHz ^ 4) +} + +#' Get pure-tone height +#' +#' Returns the pure-tone heights (a.k.a. critical-band rates) +#' of pure tones of given frequencies. +#' Equation 3 in \insertCite{Parncutt1994;textual}{incon}. +#' @param kHz Numeric vector of frequencies in kHz. +#' @return Numeric vector of corresponding pure-tone heights, +#' with units of equivalent rectangular bandwidths (ERBs). +#' @references +#' \insertAllCited{} +#' @export +get_pure_tone_height <- function(kHz) { + H1 <- 11.17 + H0 <- 43.0 + f1 <- 0.312 + f2 <- 14.675 + H1 * log((kHz + f1) / (kHz + f2)) + H0 +} + +#' Get partial masking level +#' +#' Returns the effective reduction in dB of the audible level +#' of a masked pure tone (maskee) on account of a masking pure tone (masker). +#' Equation 4 in \insertCite{Parncutt1994;textual}{incon}. +#' @param masker_auditory_level Numeric vector of masker auditory levels. +#' @param masker_pure_tone_height Numeric vector of masker pure tone heights. +#' @param maskee_auditory_level Numeric vector of maskee auditory levels. +#' @param maskee_pure_tone_height Numeric vector of maskee pure tone heights. +#' @param k_m Parameter \code{k_m} in \insertCite{Parncutt1994;textual}{incon}. +#' represents the masking pattern gradient for a pure tone, +#' with units of dB per critical band. +#' Parncutt & Strasburger use a value of 12 in their examples, +#' but imply that 12-18 is a typical range of values for this parameter. +#' @return Matrix where element [i, j] gives the level of masking +#' for masker j on maskee i. +#' @references +#' \insertAllCited{} +#' @export +get_partial_masking_level <- function(masker_auditory_level, + masker_pure_tone_height, + maskee_auditory_level, + maskee_pure_tone_height, + k_m) { + assertthat::assert_that( + length(masker_auditory_level) == length(masker_pure_tone_height), + length(maskee_auditory_level) == length(maskee_pure_tone_height) + ) + ncol <- length(masker_auditory_level) + nrow <- length(maskee_auditory_level) + # Masker matrices + masker_auditory_level_matrix <- matrix( + data = rep(masker_auditory_level, each = nrow), + nrow = nrow, ncol = ncol, byrow = FALSE + ) + masker_pure_tone_height_matrix <- matrix( + data = rep(masker_pure_tone_height, each = nrow), + nrow = nrow, ncol = ncol, byrow = FALSE + ) + # Maskee matrix + maskee_pure_tone_height_matrix <- matrix( + data = rep(maskee_pure_tone_height, each = ncol), + nrow = nrow, ncol = ncol, byrow = TRUE + ) + # Result + masker_auditory_level_matrix - + k_m * abs( + masker_pure_tone_height_matrix - maskee_pure_tone_height_matrix + ) +} + +#' Get overall masking level +#' +#' Returns overall masking levels for a set of pure tones +#' that are assumed to be playing simultaneously. +#' Corresponds to Parncutt & Strasburger (1994) Equation 5. +#' @param auditory_level Numeric vector of auditory levels +#' @param pure_tone_height Numeric vector of pure tone heights +#' @param k_m See \code{\link{parn94_params}()}. +#' @return Numeric vector of overall masking levels (dB). +#' @references +#' \insertAllCited{} +#' @export +get_overall_masking_level <- function(auditory_level, + pure_tone_height, + k_m) { + partial_mask_matrix <- get_partial_masking_level( + masker_auditory_level = auditory_level, + masker_pure_tone_height = pure_tone_height, + maskee_auditory_level = auditory_level, + maskee_pure_tone_height = pure_tone_height, + k_m = k_m + ) + # Tones don't mask themselves + diag(partial_mask_matrix) <- 0 + # Sum over maskers to find the masking for each maskee + apply( + partial_mask_matrix, 1, + function(x) { + max(c( + 20 * log(sum(10 ^ (x / 20)), base = 10), + 0 + )) + } + ) +} + +#' Get audible level +#' +#' Returns the audible level for set of pure tones subject +#' to a given masking pattern. +#' Corresponds to Equation 6 of \insertCite{Parncutt1994;textual}{incon}. +#' @param auditory_level Numeric vector of auditory levels for +#' a set of pure tones (dB). +#' @param overall_masking_level Numeric vector of overall masking levels +#' for a set of pure tones (dB). +#' @return Numeric vector of audible levels (dB). +#' @references +#' \insertAllCited{} +#' @export +get_pure_tone_audible_level <- function(auditory_level, overall_masking_level) { + assertthat::assert_that(length(auditory_level) == length(overall_masking_level)) + pmax(0, auditory_level - overall_masking_level) +} + +#' Get audibility +#' +#' Returns the audibility of a set of pure tone components as a +#' function of their audible levels. +#' Corresponds to Equation 7 of \insertCite{Parncutt1994;textual}{incon}. +#' @param pure_tone_audible_level Numeric vector of audible levels (dB). +#' @param al_0 constant (see Equation 7 of Parncutt & Strasburger (1994)). +#' @return Numeric vector of pure tone audibilities. +#' @references +#' \insertAllCited{} +#' @export +get_pure_tone_audibility <- function(pure_tone_audible_level, al_0) { + 1 - exp(- pure_tone_audible_level / al_0) +} + +#' Get tone salience +#' +#' Gets the salience of different tones within a sonority with +#' reference to the combined (complex and pure) spectrum. +#' Salience can be interpreted as the probability of consciously +#' perceiving a given pitch. +#' @param combined_audibility Numeric vector corresponding to the +#' audibilities of each tone in the combined (pure and complex) spectrum. +#' @param k_s Numeric scalar; \insertCite{Parncutt1994;textual}{incon} +#' set this to 0.5. +#' @return Numeric vector of saliences of the same length as +#' \code{combined_audibility}, giving the salience of each respective tone. +#' @references +#' \insertAllCited{} +#' @export +get_tone_salience <- function(combined_audibility, k_s) { + if (length(combined_audibility) == 0) { + numeric() + } else { + a_max <- max(combined_audibility) + m_prime <- sum(combined_audibility) / a_max + m <- m_prime ^ k_s + (combined_audibility / a_max) * + (m / m_prime) + } +} +#' @importFrom magrittr "%>%" +NULL + +#' @importFrom stats cor +NULL + +#' @importFrom methods .valueClassTest +NULL + +#' @importFrom methods new +NULL + +# `.` <- NULL + +utils::globalVariables(c(".", ".data"), package = "incon") +#' Get multiplicity +#' +#' Computes the multiplicity of a sound, after +#' \insertCite{Parncutt1994;textual}{incon}. +#' @param x Object to analyse. +#' @param k_s Numeric scalar, parameter from Parncutt & Strasburger (1994). +#' @param ... Further parameters to pass to \code{\link{incon}()}. +#' @return Multiplicity, a numeric scalar. +#' @rdname multiplicity +#' @references +#' \insertAllCited{} +#' @export +multiplicity <- function(x, k_s = parn94_params()$k_s, ...) { + UseMethod("multiplicity") +} + +#' @rdname multiplicity +#' @export +multiplicity.parn94 <- function(x, k_s = parn94_params()$k_s, ...) { + audibility <- x$combined_spectrum$combined_audibility + if (length(audibility) == 0) { + 0 + } else { + a_max <- max(audibility) + m_prime <- sum(audibility) / a_max + m <- m_prime ^ k_s + m + } +} + +#' @rdname multiplicity +#' @export +multiplicity.default <- function(x, k_s = parn94_params()$k_s, ...) { + x <- parn94(x, ...) + multiplicity(x, k_s = k_s) +} +#' Model parameters +#' +#' This function compiles parameters for Parncutt's psychoacoustic model. +#' The parameters are defined with reference to +#' \insertCite{Parncutt1994;textual}{incon}. +#' @param unit_amplitude_in_dB (Numeric scalar) Describes the number of decibels +#' to assign to a spectral component of amplitude 1. +#' By default, amplitude 1 corresponds to the amplitude of each chord pitch's +#' fundamental frequency (though this can be changed by expressing input chords +#' as pitch-class sparse spectra, see \code{\link[hrep]{sparse_pi_spectrum}()}). +#' @param template_num_harmonics (Integerish scalar) Number of harmonics to +#' include in the spectral template, including the fundamental frequency. +#' @param template_roll_off (Numeric scalar) Roll-off rate for the +#' harmonic template. This parameter is passed to +#' \code{\link[hrep]{sparse_pi_spectrum}()}. +#' @param k_t (Numeric scalar) Relative perceptual weighting of complex versus pure tones. +#' @param k_p (Numeric scalar) Scaling coefficient for pure sonorousness. +#' @param k_c (Numeric scalar) Scaling coefficient for complex sonorousness. +#' @param k_s (Numeric scalar) Scaling coefficient for multiplicity. +#' @param k_m (Numeric scalar) Gradient of a pure tone's masking pattern. +#' @param al_0 (Numeric scalar) Audibility saturation rate. +#' @param min_midi (Numeric scalar) Lowest MIDI pitch considered by the model. +#' @param max_midi (Numeric scalar) Highest MIDI pitch considered by the model. +#' @return A list of parameters which can be passed to analysis functions +#' such as \code{\link{parn94}}. +#' @references +#' \insertAllCited{} +#' @export +parn94_params <- function( + unit_amplitude_in_dB = 60, + template_num_harmonics = 11, + template_roll_off = 1, + k_t = 3, + k_p = 0.5, + k_c = 0.2, + k_s = 0.5, + k_m = 12, + al_0 = 15, + min_midi = 0, + max_midi = 120 +) { + as.list(environment()) +} +#' Parncutt & Strasburger (1994) +#' +#' This function analyses a sonority using Richard Parncutt's +#' psychoacoustic model of harmony, as described in +#' \insertCite{Parncutt1994;textual}{incon}. +#' +#' @param x Object to analyse, +#' which will be coerced to an object of class +#' \code{\link[hrep]{sparse_pi_spectrum}}. +#' Various input types are possible: +#' * Numeric vectors will be treated as vectors of MIDI note numbers, +#' which will be expanded into their implied harmonics. +#' * A two-element list can be used to define a harmonic spectrum. +#' The first element should be a vector of MIDI note numbers, +#' the second a vector of amplitudes. +#' * The function also accepts classes from the \code{hrep} package, +#' such as produced by \code{\link[hrep]{pi_chord}()} and +#' \code{\link[hrep]{sparse_pi_spectrum}()}. +#' +#' @param par Parameter list as created by \code{\link{parn94_params}()}. +#' +#' @param ... Parameters to pass to \code{\link[hrep]{sparse_pi_spectrum}}. +#' * \code{num_harmonics}: Number of harmonics to use when expanding +#' chord tones into their implied harmonics. +#' * \code{roll_off}: Rate of amplitude roll-off for the harmonics. +#' +#' @return An list of class \code{parn94}, comprising the following components: +#' \item{pure_spectrum}{A tibble describing the sonority's pure spectrum. +#' The pure spectrum is a spectral representation of the input sound +#' after auditory masking, but before pattern matching.} +#' \item{pure_spectrum}{A tibble describing the sonority's complex spectrum. +#' The complex spectrum is created from the pure spectrum through +#' harmonic template matching.} +#' \item{combined_spectrum}{A tibble describing the sonority's combined spectrum. +#' The combined spectrum corresponds to the combination of the +#' pure and complex spectra.} +#' \item{par}{A list comprising the parameters used to perform the analysis, +#' as created by \code{\link{parn94_params}()}.} +#' +#' @references +#' \insertAllCited{} +#' +#' @rdname parn94 +#' +#' @md +#' +#' @export +parn94 <- function(x, par = parn94_params(), ...) { + UseMethod("parn94") +} + +#' @rdname parn94 +#' @export +parn94.default <- function(x, par = parn94_params(), ...) { + x <- hrep::sparse_pi_spectrum(x, digits = 0, ...) + parn94(x, par = par) +} + +#' @rdname parn94 +#' @export +parn94.sparse_pi_spectrum <- function(x, par = parn94_params(), ...) { + x <- preprocess_spectrum(x, par) + .parn94() %>% + add_pure_spectrum(x, par) %>% + add_complex_spectrum(par) %>% + add_combined_spectrum(par) %>% + add_par(par) +} + +preprocess_spectrum <- function(x, par) { + if (!hrep::is.equal_tempered(x)) stop("input must be equal-tempered") + x[hrep::pitch(x) >= par$min_midi & + hrep::pitch(x) <= par$max_midi, ] +} + +add_par <- function(x, par) { + x$par <- par + x +} + +.parn94 <- function() { + x <- list() + class(x) <- "parn94" + x +} +#' Get pitch commonality +#' +#' Gets the pitch commonality between two sonorities, after +#' \insertCite{Parncutt1994;textual}{incon}. +#' +#' @param x The first sonority to compare, passed to \code{\link{pitch_salience}()}. +#' Typically will be a numeric vector of MIDI pitches. +#' +#' @param y The second sonority to compare, passed to \code{\link{pitch_salience}()}. +#' Typically will be a numeric vector of MIDI pitches. +#' +#' @param ... Further arguments to pass to \code{\link{pitch_salience}()}. +#' +#' @return Pitch commonality, as a numeric scalar. +#' +#' @references +#' \insertAllCited{} +#' +#' @export +pitch_commonality <- function(x, y, ...) { + s1 <- pitch_salience(x, ...) + s2 <- pitch_salience(y, ...) + if (length(s1) == 0L || length(s2) == 0L) return(as.numeric(NA)) + + if (attr(s1, "min_midi") != attr(s2, "min_midi") || + attr(s1, "max_midi") != attr(s2, "max_midi")) + stop("x and y must be created with identical 'min_midi' and max_midi' ", + "parameters") + + if (all(s1 == 0 ) || all(s2 == 0)) + as.numeric(NA) else + cor(s1, s2) +} +#' Get pitch distance +#' +#' Gets the pitch distance between two sonorities, after +#' \insertCite{Parncutt1994;textual}{incon}. +#' +#' @param x The first sonority to compare, passed to \code{\link{pitch_salience}()}. +#' Typically will be a numeric vector of MIDI pitches. +#' +#' @param y The second sonority to compare, passed to \code{\link{pitch_salience}()}. +#' Typically will be a numeric vector of MIDI pitches. +#' +#' @param ... Further arguments to pass to \code{\link{pitch_salience}()}. +#' +#' @return Pitch distance, as a numeric scalar. +#' +#' @references +#' \insertAllCited{} +#' @export +pitch_distance <- function(x, y, ...) { + s1 <- pitch_salience(x, ...) + s2 <- pitch_salience(y, ...) + if (length(s1) == 0L || length(s2) == 0L) return(as.numeric(NA)) + + min_midi <- attr(s1, "min_midi") + max_midi <- attr(s1, "max_midi") + if (min_midi != attr(s2, "min_midi") || + max_midi != attr(s2, "max_midi")) + stop("x and y must be created with identical 'min_midi' and max_midi' ", + "parameters") + + # We define some matrices that will allow us to vectorise our calculation - + # see Equation 17 of Parncutt & Strasburger (1994). + # Element [i, j] of each matrix corresponds to one combination of P / P' + # in Equation 17. + + dim <- length(s1) + m1 <- matrix(data = rep(seq(from = min_midi, to = max_midi), each = dim), + nrow = dim, byrow = TRUE) + m2 <- matrix(data = rep(seq(from = min_midi, to = max_midi), each = dim), + nrow = dim, byrow = FALSE) + dist <- abs(m1 - m2) + s1_mat <- matrix(data = rep(s1, each = dim), nrow = dim, byrow = TRUE) + s2_mat <- matrix(data = rep(s2, each = dim), nrow = dim, byrow = FALSE) + + sum(s1_mat * s2_mat * dist) - + sqrt(sum(s1_mat * t(s1_mat) * dist) * + sum(t(s2_mat) * s2_mat * dist)) +} +#' Get pitch salience +#' +#' Analyses the pitch salience of a sonority, after +#' \insertCite{Parncutt1994;textual}{incon}. +#' @param x Object to analyse, passed to \code{\link{parn94}()}. +#' @param ... Further arguments to pass to \code{\link{parn94}()}. +#' @return Returns a vector where each element describes +#' the salience of a different chromatic pitch. +#' The first element of this vector corresponds to the +#' \code{min_midi} argument from \code{\link{parn94_params}}, +#' and the last element corresponds to the \code{max_midi} argument. +#' @references +#' \insertAllCited{} +#' @rdname pitch_salience +#' @export +pitch_salience <- function(x, ...) { + UseMethod("pitch_salience") +} + +#' @rdname pitch_salience +#' @export +pitch_salience.default <- function(x, ...) { + x <- parn94(x, ...) + pitch_salience(x) +} + +#' @rdname pitch_salience +#' @export +pitch_salience.pitch_salience <- function(x, ...) { + x +} + +#' @rdname pitch_salience +#' @export +pitch_salience.parn94 <- function(x, ...) { + vec <- numeric(x$par$max_midi - x$par$min_midi + 1) + ind <- x$combined_spectrum$pitch - x$par$min_midi + 1 + val <- x$combined_spectrum$salience + vec[ind] <- val + .pitch_salience(vec, x$par$min_midi, x$par$max_midi) +} + +.pitch_salience <- function(x, min_midi, max_midi) { + class(x) <- "pitch_salience" + attr(x, "min_midi") <- min_midi + attr(x, "max_midi") <- max_midi + x +} +#' Get pure sonorousness +#' +#' Computes the pure sonorousness of a sound, after +#' \insertCite{Parncutt1994;textual}{incon}. +#' @param x Object to analyse. +#' @param k_p Parncutt & Strasburger (1994) set this to 0.5 (p. 105). +#' @param ... Further parameters to pass to \code{\link{parn94}()}. +#' @return Pure sonorousness, a numeric scalar. +#' @rdname pure_sonor +#' @references +#' \insertAllCited{} +#' @export +pure_sonor <- function(x, k_p = parn94_params()$k_p, ...) { + UseMethod("pure_sonor") +} + +#' @rdname pure_sonor +#' @export +pure_sonor.parn94 <- function(x, k_p = parn94_params()$k_p, ...) { + k_p * sqrt(sum(x$pure_spectrum$pure_tone_audibility ^ 2)) +} + +#' @rdname pure_sonor +#' @export +pure_sonor.default <- function(x, k_p = parn94_params()$k_p, ...) { + x <- parn94(x, ...) + pure_sonor(x, k_p = k_p) +} diff --git a/R/model-stolz15.R b/R/model-stolz15.R new file mode 100644 index 0000000..93381f8 --- /dev/null +++ b/R/model-stolz15.R @@ -0,0 +1,122 @@ +#' Smoothed log periodicity +#' +#' This function computes a chord's smoothed logarithmic periodicity, +#' after \insertCite{Stolzenburg2015;textual}{incon}. +#' @param x Sonority to analyse. +#' This will be coerced to an object of class \code{\link[hrep]{pi_chord}}. +#' Numeric inputs will be interpreted as MIDI note numbers. +#' @param d (numeric scalar): +#' Maximal allowed error in the algorithm's +#' interval approximation step, expressed as +#' a fraction of the original interval. +#' The default value, 0.011, corresponds to 'Rational Tuning II' +#' in Stolzenburg's paper. +#' @return A numeric scalar identifying the chord's periodicity. +#' High values mean a higher period length, lower periodicity, +#' and lower consonance. +#' @references +#' \insertAllCited{} +#' @rdname smooth_log_periodicity +#' @export +smooth_log_periodicity <- function(x, d = 0.011) { + UseMethod("smooth_log_periodicity") +} + +#' @rdname smooth_log_periodicity +#' @export +smooth_log_periodicity.default <- function(x, d = 0.011) { + x <- hrep::pi_chord(x) + do.call(smooth_log_periodicity, as.list(environment())) +} + +#' @rdname smooth_log_periodicity +#' @export +smooth_log_periodicity.pi_chord <- function(x, d = 0.011) { + checkmate::qassert(d, "N1(0,)") + chord <- as.numeric(x) + mean(vapply(seq_along(x), function(i) { + tmp_chord <- x - x[i] + log2(relative_periodicity(stolz15_rationalise_chord(tmp_chord, d = d))) + }, numeric(1))) +} + +# See DOI: 10.1080/17459737.2015.1033024 +# @param x Number to approximate +# @param d Tolerance ratio +stolz15_fraction <- function(x, d, verbose = FALSE) { + x_min <- (1 - d) * x + x_max <- (1 + d) * x + a_l <- floor(x) + b_l <- 1 + a_r <- floor(x) + 1 + b_r <- 1 + a <- round(x) + b <- 1 + while(a / b < x_min || x_max < a / b) { + x_0 <- 2 * x - a / b + if (x < a / b) { + a_r <- a + b_r <- b + k <- floor((x_0 * b_l - a_l) / (a_r - x_0 * b_r)) + a_l <- a_l + k * a_r + b_l <- b_l + k * b_r + } else { + a_l <- a + b_l <- b + k <- floor((a_r - x_0 * b_r) / (x_0 * b_l - a_l)) + a_r <- a_r + k * a_l + b_r <- b_r + k * b_l + } + a <- a_l + a_r + b <- b_l + b_r + if (verbose) message("a = ", a, ", b = ", b) + } + c(a, b) +} + +# Uses rational tuning system 2 +# Non-integer inputs permitted +# @param d = 0.011 corresponds to rational tuning 2 +get_rational_interval <- function(x, d) { + stopifnot(length(x) == 1L) + octave <- floor(x / 12) + pitch_class <- x %% 12 + res <- stolz15_fraction(2 ^ (pitch_class / 12), d = 0.011) + while (octave != 0) { + if (octave < 0) { + res <- half_fraction(res) + octave <- octave + 1L + } else if (octave > 0) { + res <- double_fraction(res) + octave <- octave - 1L + } + } + res +} + +half_fraction <- function(x) { + stopifnot(length(x) == 2L) + if (x[1] %% 2L == 0L) x[1] <- x[1] / 2L else x[2] <- x[2] * 2L + x +} + +double_fraction <- function(x) { + stopifnot(length(x) == 2L) + if (x[2] %% 2L == 0L) x[2] <- x[2] / 2L else x[1] <- x[1] * 2L + x +} + +stolz15_rationalise_chord <- function(x, d) { + sapply(x, function(y) get_rational_interval(y, d)) +} + +relative_periodicity <- function(x) { + stopifnot(is.matrix(x), nrow(x) == 2, ncol(x) > 0L) + stolz15_lcm(x[2, ]) * x[1, 1] / x[2, 1] +} + +stolz15_lcm <- function(x) { + if (length(x) == 1L) x else if (length(x) == 2L) { + gmp::lcm.default(x[1], x[2]) + } else stolz15_lcm(c(x[1], stolz15_lcm(x[-1]))) +} diff --git a/R/model-wang13.R b/R/model-wang13.R new file mode 100644 index 0000000..dc35e27 --- /dev/null +++ b/R/model-wang13.R @@ -0,0 +1,1038 @@ +approx2 <- function(x, y, new_x) { + assertthat::assert_that( + length(x) == length(y), + length(x) > 1 + ) + y <- y[order(x)] + x <- x[order(x)] + n <- length(x) + ifelse( + new_x < x[1], + y[1] + (new_x - x[1]) * (y[2] - y[1]) / (x[2] - x[1]), + ifelse( + new_x > x[n], + y[n] + (new_x - x[n]) * (y[n] - y[n - 1]) / (x[n] - x[n - 1]), + approx(x, y, xout = new_x)$y + ) + ) +} + +compile_detail <- function(x) { + x$spectrum_input <- data.frame(frequency_Hz = x$frequency_Hz, + level_dB = x$level_dB) + x$spectrum_after_ear_filtering <- data.frame(frequency_Hz = x$frequency_Hz, + level_dB = x$level_dB_filtered) + x[c( + "spectrum_input", + "spectrum_after_ear_filtering", + "channel_wave_forms", + "channel_envelopes", + "filtered_channel_envelopes", + "modulation_indices", + "phase_impact_factors", + "specific_roughnesses", + "total_roughness" + )] +} + +get_channel_sound_excitation_levels <- function(frequency_Hz, + level_dB_filtered) { + frequency_bark <- convert_Hz_to_bark(frequency_Hz) + lapply( + seq_len(47), + function(channel_num) { + get_channel_sound_excitation_level( + freq_Hz = frequency_Hz, + freq_bark = frequency_bark, + sound_intensity_level = level_dB_filtered, + channel_num = channel_num) + } + ) +} + +# @param freq_Hz Frequency in Hz (numeric vector) +# @param freq_bark Corresponding frequency in barks (numeric vector) +# @param sound_intensity_level Sound intensity level (numeric vector, matches with freq_Hz) +# @param channel_num Critical band filter number (should be a scalar integer between 1 and 47) +# @return Numeric vector giving the excitation level for the respective channel for each frequency listed in \code{freq_Hz} +get_channel_sound_excitation_level <- function(freq_Hz, + freq_bark, + sound_intensity_level, + channel_num) { + assertthat::assert_that( + is.numeric(freq_bark), is.numeric(sound_intensity_level), + length(freq_bark) == length(sound_intensity_level), + assertthat::is.scalar(channel_num), is.numeric(channel_num) + ) + channel_centre_bark <- 0.5 * channel_num + df <- data.frame( + freq_Hz = freq_Hz, + freq_bark = freq_bark, + sound_intensity_level = sound_intensity_level, + region = NA + ) + # Assign components to regions + df$region[0 <= df$freq_bark & freq_bark < channel_centre_bark - 0.5] <- "upper_slope" + df$region[channel_centre_bark - 0.5 <= freq_bark & + freq_bark <= channel_centre_bark + 0.5] <- "centre" + df$region[channel_centre_bark + 0.5 < freq_bark & + freq_bark <= 47] <- "lower_slope" + # Calculate SELs for components within the critical band + df$sound_excitation_level[df$region == "centre"] <- + df[df$region == "centre", ] %>% + (function(df) df$sound_intensity_level) + # ... and for components below the critical band... + df$sound_excitation_level[df$region == "upper_slope"] <- + df[df$region == "upper_slope", ] %>% + (function(df) { + df$sound_intensity_level + + ((channel_centre_bark + 0.5) - df$freq_bark) * + (- 24 - (230 / df$freq_Hz) + 0.2 * df$sound_intensity_level) + }) + # ... and for components above the critical band + df$sound_excitation_level[df$region == "lower_slope"] <- + df[df$region == "lower_slope", ] %>% + (function(df) { + df$sound_intensity_level + + ((channel_centre_bark - 0.5) - df$freq_bark) * 27 + }) + df$sound_excitation_level +} + +get_channel_wave_form <- function(excitation_levels, + frequency_Hz) { + # Excitation levels are in decibels, so we need to convert back + # to a linear scale. The units for this linear scale don't matter, + # because later on we normalise by the RMS of the waveform. + assertthat::assert_that( + all(frequency_Hz < 44000), + all(frequency_Hz >= 0.5) + ) + # This bit is inefficient for dense spectra + # (i.e. when frequency_Hz is long) + amplitudes <- 10 ^ (excitation_levels / 20) + x <- rep(0, times = 44000) + for (i in seq_along(frequency_Hz)) { + tmp_freq <- round(frequency_Hz[i]) + tmp_amp <- amplitudes[i] + x[tmp_freq] <- hrep::sum_amplitudes( + x[tmp_freq], + tmp_amp, + coherent = FALSE + ) + } + Re(fft(x, inverse = TRUE) / length(x)) +} + +get_channel_envelope <- function(x) { + hht::HilbertEnvelope(hht::HilbertTransform(x)) %>% + (function(x) x - mean(x)) # center it +} + +filter_channel_envelope <- function(i, channel_envelope) { + ft <- fft(channel_envelope) + ft.coef <- Mod(ft) + ft.freq <- seq(from = 0, to = 44000 - 1) + ft.freq <- ifelse(ft.freq >= 22000, # Nyquist frequency + 44000 - ft.freq, + ft.freq) + ft.coef_new <- ft.coef * envelope_weight(freq_Hz = abs(ft.freq), + channel_num = i) + waveform <- Re(fft(ft.coef_new, inverse = TRUE) / length(ft.coef_new)) + waveform <- waveform - mean(waveform) + waveform +} + +get_modulation_index <- function(filtered_channel_envelope, + channel_wave_form) { + sqrt(mean(filtered_channel_envelope ^ 2)) / + sqrt(mean(channel_wave_form ^ 2)) +} + +get_phase_impact_factor <- function(i, filtered_channel_envelopes) { + if (i == 1) { + cor( + filtered_channel_envelopes[[1]], + filtered_channel_envelopes[[2]] + ) ^ 2 + } else if (i == 47) { + cor( + filtered_channel_envelopes[[46]], + filtered_channel_envelopes[[47]] + ) ^ 2 + } else { + cor( + filtered_channel_envelopes[[i - 1]], + filtered_channel_envelopes[[i]] + ) * + cor( + filtered_channel_envelopes[[i]], + filtered_channel_envelopes[[i + 1]] + ) + } +} + +get_channel_weight <- function(channel_num) { + assertthat::assert_that( + is.numeric(channel_num), + all(round(channel_num) == channel_num), + all(channel_num > 0), + all(channel_num < 48) + ) + approx( + x = c(1, 5, 10, 17, 19, 24, 27, 47), + y = c(0.41, 0.82, 0.83, 1.12, 1.12, 1, 0.8, 0.58), + xout = channel_num + )$y +} + +# Get ear transmission coefficients +# +# Gets ear transmission coefficients according to Figure 3 of Wang et al. (2013). +# This is a linear approximation to the graph provided in the paper +# (the paper does not provide an equation for the curve, unfortunately). +# @param freq Numeric vector of frequencies +# @return Numeric vector of ear transmission coefficients. +# These coefficients describe a filter that can be applied to incoming spectra +# to simulate the filtering of the ear. +# \insertRef{Wang2013}{incon} +ear_transmission <- function(freq) { + log_freq <- log(freq, base = 10) + ifelse( + log_freq < 3.1, + 0, + ifelse( + log_freq > 4.25, + 30 + (log_freq - 4.25) * 100, + approx( + x = c(3.1, 3.4, 3.5, 4, 4.25), + y = c(0, -5, -5, 5, 30), + method = "linear", rule = 2, + xout = log_freq + )$y + ) + ) +} + +convert_Hz_to_bark <- function(freq_Hz) { + freq_kHz <- freq_Hz / 1000 + ifelse( + freq_kHz <= 1.5, + 11.82 * atan(1.21 * freq_kHz), + 5 * log(freq_kHz / 1.5) + 12.61 + ) +} + +get_critical_bandwidth <- function(freq_Hz) { + ifelse(freq_Hz < 500, 100, freq_Hz / 5) +} + +# Figure 5 of Wang et al. +envelope_weight <- function(freq_Hz, channel_num) { + assertthat::assert_that( + is.numeric(freq_Hz), + is.numeric(channel_num), assertthat::is.scalar(channel_num), + channel_num > 0, channel_num < 48, + round(channel_num) == channel_num + ) + if (channel_num < 5) { + approx2( + x = c(0, 20, 30, 150, 250, 350), + y = c(0, 0.8, 1, 0.475, 0.2, 0.02), + new_x = freq_Hz + ) + } else if (channel_num < 16) { + approx2( + x = c(0, 30, 55, 150, 400), + y = c(0, 0.8, 1, 0.675, 0.1), + new_x = freq_Hz + ) + } else if (channel_num < 21) { + approx2( + x = c(0, 50, 77, 165, 250, 400), + y = c(0, 0.85, 1, 0.82, 0.48, 0.225), + new_x = freq_Hz + ) + } else if (channel_num < 42) { + approx2( + x = c(0, 50, 77, 100, 250, 400), + y = c(0, 0.9, 1, 0.95, 0.48, 0.225), + new_x = freq_Hz + ) + } else { + approx2( + x = c(0, 50, 70, 85, 140, 400), + y = c(0, 0.95, 1, 0.955, 0.69, 0.225), + new_x = freq_Hz + ) + } +} + +get_specific_roughness <- function(channel_weight, + phase_impact_factor, + modulation_index, + include_phase_impact_factors) { + if (include_phase_impact_factors) { + (channel_weight * phase_impact_factor * modulation_index) ^ 2 + } else { + (channel_weight * modulation_index) ^ 2 + } +} + +play_channel_wave_forms <- function(channel_wave_forms, + channel_num, + scale_to_other_channels = TRUE, + ...) { + if (!requireNamespace("tuneR")) + stop("tuneR must be installed before continuing") + peak <- if (scale_to_other_channels) { + channel_wave_forms %>% + vapply(function(x) max(abs(x)), numeric(1)) %>% + max + } else { + max(abs(channel_wave_forms[[channel_num]])) + } + play_wave_form(x = channel_wave_forms[[channel_num]], + peak = peak, + ...) +} + + +play_wave_form <- function(x, + sample_rate = 44e3, + fade_samples = 1e3, + bit = 16, + peak = max(abs(x))) { + if (!requireNamespace("tuneR")) + stop("tuneR must be installed before continuing") + + if (length(x) > 2 * fade_samples) { + ind_1 <- seq(from = 1, length.out = fade_samples) + ind_2 <- seq(to = length(x), length.out = fade_samples) + x[ind_1] <- x[ind_1] * seq(from = 0, to = 1, length.out = fade_samples) + x[ind_2] <- x[ind_2] * seq(from = 1, to = 0, length.out = fade_samples) + } + + u <- x %>% + magrittr::divide_by(peak * 1.5) %>% + magrittr::multiply_by(2 ^ (bit - 1) - 1) %>% + round + tuneR::play(tuneR::Wave(u, samp.rate = sample_rate, bit = bit), "play") +} + + +play_sparse_spectrum <- function( + frequency, + amplitude, + seconds = 1, + sample_rate = 44e3, + ... +) { + sparse_spectrum_to_waveform(frequency, amplitude, seconds, sample_rate) %>% + play_wave_form(sample_rate = sample_rate, ...) +} + +# Note: this could be done more efficiently with ifft +sparse_spectrum_to_waveform <- function( + frequency, + amplitude, + seconds, + sample_rate +) { + x <- seq(from = 0, to = seconds, length.out = sample_rate * seconds) + y <- mapply( + function(freq, amplitude) { + sin(2 * pi * freq * x) * amplitude + }, + frequency, + amplitude + ) %>% rowSums +} + +plot_modulation_indices_wang <- function(modulation_indices, + theme = cowplot::theme_cowplot()) { + if (!requireNamespace("ggplot2")) + stop("ggplot2 must be installed before continuing") + assertthat::assert_that( + is.numeric(modulation_indices), + length(modulation_indices) == 47 + ) + df <- data.frame( + x = seq_along(modulation_indices), + y = modulation_indices + ) + ggplot2::ggplot(df, ggplot2::aes_string(x = "x", y = "y")) + + ggplot2::geom_line() + + ggplot2::scale_x_continuous("Channel number") + + ggplot2::scale_y_continuous("Modulation index") + + theme + + ggplot2::theme(aspect.ratio = 1) +} + +plot_phase_impact_factors_wang <- function(phase_impact_factors, + theme = cowplot::theme_cowplot()) { + if (!requireNamespace("ggplot2")) + stop("ggplot2 must be installed before continuing") + assertthat::assert_that( + is.numeric(phase_impact_factors), + length(phase_impact_factors) == 47 + ) + df <- data.frame( + x = seq_along(phase_impact_factors), + y = phase_impact_factors + ) + ggplot2::ggplot(df, ggplot2::aes_string(x = "x", y = "y")) + + ggplot2::geom_line() + + ggplot2::scale_x_continuous("Channel number") + + ggplot2::scale_y_continuous("Phase impact factor") + + theme + + ggplot2::theme(aspect.ratio = 1) +} + +plot_specific_roughnesses_wang <- function(specific_roughnesses, + theme = cowplot::theme_cowplot()) { + if (!requireNamespace("ggplot2")) + stop("ggplot2 must be installed before continuing") + assertthat::assert_that( + is.numeric(specific_roughnesses), + length(specific_roughnesses) == 47 + ) + df <- data.frame( + x = seq_along(specific_roughnesses), + y = specific_roughnesses + ) + ggplot2::ggplot(df, ggplot2::aes_string(x = "x", y = "y")) + + ggplot2::geom_line() + + ggplot2::scale_x_continuous("Channel number") + + ggplot2::scale_y_continuous("Specific roughness") + + theme + + ggplot2::theme(aspect.ratio = 1) +} + +plot_waveform <- function( + x, + sample_rate = 44000, + range_sec = c(0, 0.2), + theme = cowplot::theme_cowplot() +) { + if (!requireNamespace("ggplot2")) + stop("ggplot2 must be installed before continuing") + df <- data.frame( + t = seq(from = 0, by = 1 / sample_rate, length.out = length(x)), + x = x + ) %>% + (function(df) df[df$t >= range_sec[1] & df$t <= range_sec[2], ]) + ggplot2::ggplot(df, ggplot2::aes_string(x = "t", y = "x")) + + ggplot2::geom_line(alpha = 0.5) + + ggplot2::scale_x_continuous("Time (sec)") + + ggplot2::scale_y_continuous("Instantaneous amplitude") + + theme + + ggplot2::theme(aspect.ratio = 1) +} + +plot_filtered_input_spectrum <- function(analysis) { + frequency <- analysis$spectrum_after_ear_filtering$frequency_Hz + amplitude <- hrep::dB_to_amplitude( + analysis$spectrum_after_ear_filtering$level_dB, + 60 + ) + plot_sparse_spectrum(frequency, amplitude, range_Hz = NULL) +} + +plot_sparse_spectrum <- function( + frequency, + amplitude, + resolution_Hz = 1, + range_Hz = NULL, + theme = cowplot::theme_cowplot() +) { + if (!requireNamespace("ggplot2")) + stop("ggplot2 must be installed before continuing") + if (is.null(range_Hz)) range_Hz <- c(0, max(frequency)) + df <- data.frame(freq = round(frequency), + amp = amplitude) %>% + (function(df) df[df$freq >= range_Hz[1] & + df$freq <= range_Hz[2], ]) %>% + rbind( + data.frame(freq = seq(from = range_Hz[1], + to = range_Hz[2], + by = resolution_Hz), + amp = 0) + ) %>% + (function(df) { + df[order(df$freq), ] + }) + ggplot2::ggplot(df, ggplot2::aes_string(x = "freq", y = "amp")) + + ggplot2::geom_line() + + ggplot2::scale_x_continuous("Frequency (Hz)") + + ggplot2::scale_y_continuous("Amplitude") + + theme + + ggplot2::theme(aspect.ratio = 1) +} + +format_chord <- function(x) { + paste(x, collapse = " ") +} + +enter_new_chord <- function(text, state, num_harmonics) { + tryCatch({ + set_chord(hrep::pc_chord(text), + state) + }, error = function(e){ + message("Call: ", capture.output(e$call)) + message("Message: ", e$message) + shinyjs::alert("Invalid chord") + }) +} + +set_chord <- function(chord, state) { + state$chord <- chord + state$chord_img_src <- get_chord_url(chord) +} + +plot_input_spectrum <- function(analysis) { + frequency <- analysis$spectrum_input$frequency_Hz + amplitude <- hrep::dB_to_amplitude(analysis$spectrum_input$level_dB, 60) + plot_sparse_spectrum(frequency, amplitude, range_Hz = NULL) +} + +play_input_spectrum <- function(analysis) { + frequency <- analysis$spectrum_input$frequency_Hz + amplitude <- hrep::dB_to_amplitude(analysis$spectrum_input$level_dB, 60) + play_sparse_spectrum(frequency, amplitude) +} + +play_filtered_input_spectrum <- function(analysis) { + frequency <- analysis$spectrum_after_ear_filtering$frequency_Hz + amplitude <- hrep::dB_to_amplitude( + analysis$spectrum_after_ear_filtering$level_dB, 60 + ) + play_sparse_spectrum(frequency, amplitude) +} + +get_chord_url <- function(chord, type = "png") { + assertthat::assert_that( + assertthat::is.scalar(type), + is.character(type), + type %in% c("png", "mp3", "midi") + ) + pitch <- as.integer(hrep::pi_chord(chord)) + label <- paste(pitch, collapse = "_") + src <- "http://research.pmcharrison.com/studies/HarmonyDissonance/chords/piano/%s/%s.%s" %>% + sprintf(label, label, type) + src +} + +analyse_chord <- function(x, + include_phase_impact_factors, + fundamental_dB, + num_harmonics) { + spectrum <- hrep::sparse_fr_spectrum(hrep::pi_chord(x), + num_harmonics = num_harmonics) + + shiny::withProgress( + roughness_wang( + x = spectrum, + include_phase_impact_factors = include_phase_impact_factors, + detail = TRUE, + msg = function(n, N, msg) shiny::setProgress(n, msg) + ), + min = 0, max = 7 + ) +} + +shiny_ui_sidebar <- function() { + shinydashboard::dashboardSidebar( + shinyjs::useShinyjs(), + shiny::h4("Wang et al. (2013)", + style = "padding-left: 20px"), + shinydashboard::sidebarMenu( + lapply(c( + "About", + "Input spectrum", + "Filtered spectrum", + "Channel waveforms", + "Channel envelopes", + "Filtered channel envelopes", + "Modulation indices", + "Phase impact factors", + "Specific roughnesses" + ), function(title) { + shinydashboard::menuItem(title, + tabName = gsub(" ", "_", tolower(title)), + icon = NULL) + }), + shiny::uiOutput("total_roughness") + ) + ) +} + +shiny_ui_body <- function(opt) { + shinydashboard::dashboardBody( + shiny::fluidRow( + shiny::column(6, shiny_ui_tabs(opt)), + shiny::column(6, shiny_ui_input(opt)) + ) + ) +} + +shiny_ui_tabs <- function(opt) { + shinydashboard::tabItems( + shiny_ui_tab_0(), + shiny_ui_tab_1(opt), + shiny_ui_tab_2(opt), + shiny_ui_tab_3(opt), + shiny_ui_tab_4(), + shiny_ui_tab_5(), + shiny_ui_tab_6(), + shiny_ui_tab_7(), + shiny_ui_tab_8() + ) +} + +shiny_ui_tab_0 <- function() { + shinydashboard::tabItem( + tabName = "about", + shinydashboard::box( + # title = "Input spectrum", + title = "About", + status = "primary", + width = 12, + shiny::tags$p("This interactive app analyses the roughness of musical chords", + "using Wang et al.'s (2013) algorithm.", + "Use the tabs on the left-hand side of the screen", + "to navigate through the successive stages of the model."), + shiny::tags$p("Wang, Y. S., Shen, G. Q., Guo, H., Tang, X. L., & Hamade, T. (2013).", + "Roughness modelling based on human auditory perception for", + "sound quality evaluation of vehicle interior noise.", + shiny::tags$em("Journal of Sound and Vibration"), + "332(16), 3893-3904.", + shiny::tags$a(href = "https://doi.org/10.1016/j.jsv.2013.02.030") + ) + ) + ) +} + +shiny_ui_tab_1 <- function(opt) { + shinydashboard::tabItem( + tabName = "input_spectrum", + shinydashboard::box( + # title = "Input spectrum", + title = NULL, + status = "primary", + width = 12, + shiny::tags$p("The input to the model is an acoustic spectrum."), + shiny::div(shiny::plotOutput("plot_input_spectrum"), + style = "max-width: 100%"), + if (opt$audio) shiny::actionButton("play_input_spectrum", "Play") + ) + ) +} + +shiny_ui_tab_2 <- function(opt) { + shinydashboard::tabItem( + tabName = "filtered_spectrum", + shinydashboard::box( + # title = "Filtered spectrum", + title = NULL, + status = "primary", + width = 12, + shiny::tags$p("The spectrum is filtered by the outer and middle ear."), + shiny::plotOutput("plot_filtered_input_spectrum"), + if (opt$audio) shiny::actionButton("play_filtered_input_spectrum", "Play") + ) + ) +} + +shiny_ui_tab_3 <- function(opt) { + shinydashboard::tabItem( + tabName = "channel_waveforms", + shinydashboard::box( + title = NULL, + status = "primary", + width = 12, + shiny::tags$p("Different cochlear channels selectively filter", + "for different frequency ranges."), + shiny::plotOutput("plot_channel_wave_form"), + shiny::sliderInput("channel_wave_forms_channel_num", + "Channel number", + min = 1, max = 47, value = 25, step = 1), + if (opt$audio) shiny::fluidRow( + shiny::column(3, shiny::actionButton("play_channel_wave_form", "Play", + style = "text-align: center")), + shiny::column(9, shiny::checkboxInput("normalise_volume_across_channels", + "Normalise volume across channels?", + value = TRUE)) + ) + )) +} + +shiny_ui_tab_4 <- function() { + shinydashboard::tabItem( + tabName = "channel_envelopes", + shinydashboard::box( + # title = "Channel envelopes", + title = NULL, + status = "primary", + width = 12, + shiny::tags$p("Waveform envelopes are extracted", + "for each channel."), + shiny::plotOutput("plot_channel_envelope"), + shiny::sliderInput("channel_envelopes_channel_num", "Channel number", + min = 1, max = 47, value = 25, step = 1) + )) +} + +shiny_ui_tab_5 <- function() { + shinydashboard::tabItem( + tabName = "filtered_channel_envelopes", + shinydashboard::box( + # title = "Filtered channel envelopes", + title = NULL, + status = "primary", + width = 12, + shiny::tags$p("These amplitude modulations are filtered to prioritise", + "the modulation frequencies that contribute most towards roughness."), + shiny::plotOutput("plot_filtered_channel_envelope"), + shiny::sliderInput("filtered_channel_envelopes_channel_num", "Channel number", + min = 1, max = 47, value = 25, step = 1) + )) +} + +shiny_ui_tab_6 <- function() { + shinydashboard::tabItem( + tabName = "modulation_indices", + shinydashboard::box( + # title = "Modulation indices", + title = NULL, + status = "primary", + width = 12, + shiny::tags$p("The modulation index captures the magnitude of", + "amplitude modulation for each channel."), + shiny::plotOutput("plot_modulation_indices") + )) +} + +shiny_ui_tab_7 <- function() { + shinydashboard::tabItem( + tabName = "phase_impact_factors", + shinydashboard::box( + # title = "Phase impact factors", + title = NULL, + status = "primary", + width = 12, + shiny::tags$p("Phase impact factors capture the correlation between", + "the envelopes of adjacent channels.", + "According to Wang et al. (2013), higher correlations", + "yield greater roughness."), + shiny::plotOutput("plot_phase_impact_factors") + )) +} + +shiny_ui_tab_8 <- function() { + shinydashboard::tabItem( + tabName = "specific_roughnesses", + shinydashboard::box( + # title = "Specific roughnesses", + title = NULL, + status = "primary", + width = 12, + shiny::tags$p("The roughness contribution of each critical band is", + "estimated as a function of modulation index,", + "phase impact factor, and pitch height of the critical band.", + "These are then summed to give the total roughness value."), + shiny::plotOutput("plot_specific_roughnesses") + )) +} + + +shiny_ui_input <- function(opt) { + shinydashboard::box( + shiny::p("Enter a pitch-class set to analyse.", + "The first pitch class will be taken as the bass note."), + shiny::textInput("chord", label = NULL, placeholder = "e.g. 4 0 7"), + shiny::actionButton("enter_new_chord", "Enter"), + shiny::uiOutput("current_chord_text"), + shiny::uiOutput("current_chord_image", height = "auto"), + shiny::checkboxInput("include_phase_impact_factors", + "Include phase impact factors", + value = opt$default_include_phase_impact_factors), + shiny::sliderInput("num_harmonics", "Number of harmonics", + value = opt$default_num_harmonics, + min = 1, max = 15, step = 1), + style = "text-align: center" + ) +} + +#' Wang et al. (2013) interactive demo +#' +#' Launches an interactive demo of Wang et al.'s (2013) roughness model. +#' This function requires a few additional packages to be installed; +#' you will be notified if any of these packages are missing +#' once you run \code{demo_wang()}. +#' @param audio (Scalar logical) Whether to enable playback controls +#' (currently the playback controls don't work when the app is hosted +#' on a remote server). +#' @note The demo takes the form of an Shiny app +#' (\url{https://shiny.rstudio.com/}). +#' @references +#' \insertRef{Wang2013}{incon} +#' @export +demo_wang <- function(audio = TRUE) { + pkg_suggest <- c("cowplot", "shiny", "shinyjs", "shinydashboard") + purrr::map_lgl(pkg_suggest, requireNamespace) %>% + { + if (any(!.)) { + stop("the following packages need to be installed before continuing: ", + paste(pkg_suggest[!.], collapse = ", ")) + } + } + + opt <- list( + default_chord = hrep::pc_chord(c(4, 0, 7)), + default_num_harmonics = 11, + default_include_phase_impact_factors = FALSE, + fundamental_dB = 60, + audio = audio + ) + + ui <- shinydashboard::dashboardPage( + shinydashboard::dashboardHeader(title = "Wang et al. (2013)", + disable = FALSE), + shiny_ui_sidebar(), + shiny_ui_body(opt) + ) + + server <- function(input, output) { + state <- shiny::reactiveValues( + chord = opt$default_chord, + analysis = NULL + ) + output$current_chord_text <- shiny::renderUI( + shiny::tags$p("Current chord: ", + shiny::tags$strong(as.character(state$chord))) + ) + output$current_chord_image <- shiny::renderUI( + shiny::img(src = get_chord_url(state$chord), + contentType = 'image/png', + alt = "Current chord", + style = paste("max-width: 150px;", + "border-style: solid;", + "border-width: 5px;", + "border-color: white")) + ) + shiny::observeEvent( + input$enter_new_chord, + enter_new_chord( + input$chord, + state) + ) + shiny::observe({ + state$analysis <- analyse_chord(x = state$chord, + input$include_phase_impact_factors, + opt$fundamental_dB, + input$num_harmonics) + }) + shiny::observeEvent(input$play_input_spectrum, { + play_input_spectrum(state$analysis) + }) + shiny::observeEvent(input$play_filtered_input_spectrum, { + play_filtered_input_spectrum(state$analysis) + }) + shiny::observeEvent(input$play_channel_wave_form, { + play_channel_wave_forms( + channel_wave_forms = state$analysis$channel_wave_forms, + channel_num = input$channel_wave_forms_channel_num, + scale_to_other_channels = input$normalise_volume_across_channels + ) + }) + + output$plot_input_spectrum <- shiny::renderPlot( + plot_input_spectrum(state$analysis)) + + output$plot_filtered_input_spectrum <- shiny::renderPlot( + plot_filtered_input_spectrum(state$analysis)) + + output$plot_channel_wave_form <- shiny::renderPlot( + plot_waveform( + state$analysis$channel_wave_forms[[input$channel_wave_forms_channel_num]], + range_sec = c(0, 0.05) + )) + output$plot_channel_envelope <- shiny::renderPlot( + plot_waveform( + state$analysis$channel_envelopes[[input$channel_envelopes_channel_num]], + range_sec = c(0, 0.05) + )) + output$plot_filtered_channel_envelope <- shiny::renderPlot( + plot_waveform( + state$analysis$filtered_channel_envelopes[[input$filtered_channel_envelopes_channel_num]], + range_sec = c(0, 0.05) + )) + output$plot_modulation_indices <- shiny::renderPlot({ + plot_modulation_indices_wang(state$analysis$modulation_indices) + }) + + output$plot_phase_impact_factors <- shiny::renderPlot( + plot_phase_impact_factors_wang(state$analysis$phase_impact_factors)) + + output$plot_specific_roughnesses <- shiny::renderPlot( + plot_specific_roughnesses_wang(state$analysis$specific_roughnesses)) + + output$total_roughness <- shiny::renderUI({ + x <- round(state$analysis$total_roughness, digits = 5) + shiny::showNotification(shiny::p("Roughness:", shiny::strong(x)), + type = "message", + duration = 120) + shiny::p("Output:", + shiny::strong(x), + style = "padding: 15px; font-size: 12pt") + }) + } + + # Run the application + shiny::shinyApp(ui = ui, server = server) +} + +#' Wang et al.'s (2013) roughness model +#' +#' Gets the roughness of a sonority according to the model of Wang et al. (2013). +#' @param x Object to analyse, +#' which will be coerced to an object of class +#' \code{\link[hrep]{sparse_fr_spectrum}}. +#' Various input types are possible: +#' * Numeric vectors will be treated as vectors of MIDI note numbers, +#' which will be expanded into their implied harmonics. +#' * A two-element list can be used to define a harmonic spectrum. +#' The first element should be a vector of frequencies in Hz, +#' the second a vector of amplitudes. +#' * The function also accepts classes from the \code{hrep} package, +#' such as produced by \code{\link[hrep]{pi_chord}()} and +#' \code{\link[hrep]{sparse_fr_spectrum}()}. +#' @param detail (Logical scalar) Whether to return detailed output information. +#' @param include_phase_impact_factors (Logical scalar) +#' Whether to include phase impact factors in roughness computation. +#' Set to \code{TRUE} to recover the original specifications of Wang et al. (2013). +#' However, disabling this feature (by leaving the parameter at \code{FALSE}) +#' seems to result in better estimation of perceptual consonance. +#' @param unit_amplitude_in_dB (Numeric scalar) +#' Determines the decibel level of a partial with amplitude 1. +#' When the input is a musical chord, +#' this will correspond to the decibel level of the fundamental frequencies +#' of each chord tone. +#' @param msg Function to be called to give progress updates. +#' This function should accept three arguments: +#' \code{n}, an integer identifying the current position in the pipeline, +#' \code{N}, an integer identifying the length of the pipeline, +#' and \code{msg}, a string providing a longer-format description +#' of the current position in the pipeline. +#' Pass \code{NULL} to disable progress updates. +#' @param ... Additional parameters to pass to +#' \code{\link[hrep]{sparse_fr_spectrum}}. +#' * \code{num_harmonics}: Number of harmonics to use when expanding +#' chord tones into their implied harmonics. +#' * \code{roll_off}: Rate of amplitude roll-off for the harmonics. +#' @return If \code{detail == FALSE}, a numeric vector of roughnesses, +#' otherwise a list containing detailed algorithm output. +#' @references +#' \insertRef{Wang2013}{incon} +#' @note +#' This implementation is designed for sparse input spectra, that is, +#' spectra containing only a few (< 100) components. +#' @md +#' @rdname roughness_wang +#' @export +roughness_wang <- function( + x, + detail = FALSE, + include_phase_impact_factors = FALSE, + unit_amplitude_in_dB = 60, + msg = function(n, N, msg) + if (interactive()) + message(n, "/", N, ": ", msg), + ... +) { + UseMethod("roughness_wang") +} + +#' @rdname roughness_wang +#' @export +roughness_wang.default <- function( + x, + detail = FALSE, + include_phase_impact_factors = FALSE, + unit_amplitude_in_dB = 60, + msg = function(n, N, msg) + if (interactive()) + message(n, "/", N, ": ", msg), + ... +) { + x <- hrep::sparse_fr_spectrum(x, ...) + do.call(roughness_wang, as.list(environment())) +} + +#' @rdname roughness_wang +#' @export +roughness_wang.sparse_fr_spectrum <- function( + x, + detail = FALSE, + include_phase_impact_factors = FALSE, + unit_amplitude_in_dB = 60, + msg = function(n, N, msg) + if (interactive()) + message(n, "/", N, ": ", msg), + ... +) { + frequency_Hz <- hrep::freq(x) + level_dB <- hrep::amplitude_to_dB(hrep::amp(x), unit_amplitude_in_dB) + if (is.null(msg)) msg <- function(...) NULL + + msg(1, 6, "Ear transmission...") + level_dB_filtered <- level_dB - ear_transmission(frequency_Hz) + + msg(2, 6, "Channel excitation levels...") + channel_sound_excitation_levels <- get_channel_sound_excitation_levels( + frequency_Hz = frequency_Hz, + level_dB_filtered = level_dB_filtered + ) + + # is a list of numeric vectors corresponding to the + # y values of the waveforms for each channel for time in [0s, 1s]. + # The units of y is amplitude ratios relative to the reference sound amplitude. + msg(3, 6, "Channel waveforms...") + channel_wave_forms <- purrr::map(.x = channel_sound_excitation_levels, + .f = get_channel_wave_form, + frequency_Hz) + + # These are waveforms corresponding to the signal envelopes + msg(4, 6, "Channel envelopes...") + channel_envelopes <- purrr::map(.x = channel_wave_forms, + .f = get_channel_envelope) + + # The channel envelopes are filtered to account for the different roughness + # contributions of different modulation frequencies + msg(5, 6, "Filtering channel envelopes...") + filtered_channel_envelopes <- purrr::map2(.x = seq_along(channel_envelopes), + .y = channel_envelopes, + .f = filter_channel_envelope) + + msg(6, 6, "Computing roughness...") + modulation_indices <- purrr::map2_dbl(.x = filtered_channel_envelopes, + .y = channel_wave_forms, + .f = get_modulation_index) + + phase_impact_factors <- purrr::map_dbl(.x = seq_len(47), + .f = get_phase_impact_factor, + filtered_channel_envelopes) + + specific_roughnesses <- purrr::pmap_dbl(.l = list(get_channel_weight(seq_len(47)), + phase_impact_factors, + modulation_indices), + .f = get_specific_roughness, + include_phase_impact_factors) + + total_roughness <- 0.25 * sum(specific_roughnesses) + + if (detail) + compile_detail(as.list(environment())) else + total_roughness +} diff --git a/R/models.R b/R/models.R index b55bae3..f7295ae 100644 --- a/R/models.R +++ b/R/models.R @@ -64,83 +64,83 @@ add_model <- function(label, add_model("gill_09_harmonicity", "Gill & Purves (2009)", "Periodicity/harmonicity", - "bowl18", + "incon", consonance = TRUE, spectrum_sensitive = FALSE, continuous_pitch = FALSE, f = function(x, num_harmonics, roll_off, ...) - bowl18::gill09_harmonicity(x, ...)) + gill09_harmonicity(x, ...)) add_model("har_18_harmonicity", "Harrison & Pearce (2018)", "Periodicity/harmonicity", - "har18", + "incon", consonance = TRUE, spectrum_sensitive = TRUE, continuous_pitch = TRUE, f = function(x, num_harmonics, roll_off, ...) - har18::pc_harmonicity(x, - method = "kl", - num_harmonics = num_harmonics, - rho = roll_off * 0.75, - ...)) + pc_harmonicity(x, + method = "kl", + num_harmonics = num_harmonics, + rho = roll_off * 0.75, + ...)) add_model("milne_13_harmonicity", "Milne (2013)", "Periodicity/harmonicity", - "har18", + "incon", consonance = TRUE, spectrum_sensitive = TRUE, continuous_pitch = TRUE, f = function(x, num_harmonics, roll_off, ...) - har18::pc_harmonicity(x, - method = "peak", - num_harmonics = num_harmonics, - rho = roll_off * 0.75, - ...)) + pc_harmonicity(x, + method = "peak", + num_harmonics = num_harmonics, + rho = roll_off * 0.75, + ...)) add_model("parn_88_root_ambig", "Parncutt (1988)", "Periodicity/harmonicity", - "parn88", + "incon", consonance = FALSE, spectrum_sensitive = FALSE, continuous_pitch = FALSE, f = function(x, num_harmonics, roll_off, ...) - parn88::root_ambiguity(x, ...)) + root_ambiguity(x, ...)) add_model("parn_94_complex", "Parncutt & Strasburger (1994)", "Periodicity/harmonicity", - "parn94", + "incon", consonance = TRUE, spectrum_sensitive = TRUE, continuous_pitch = FALSE, f = function(x, num_harmonics, roll_off, ...) - parn94::complex_sonor(x, - num_harmonics = num_harmonics, - roll_off = roll_off, - ...)) + complex_sonor(x, + num_harmonics = num_harmonics, + roll_off = roll_off, + ...)) add_model("stolz_15_periodicity", "Stolzenburg (2015)", "Periodicity/harmonicity", - "stolz15", + "incon", consonance = FALSE, spectrum_sensitive = FALSE, continuous_pitch = TRUE, f = function(x, num_harmonics, roll_off, ...) - stolz15::smooth_log_periodicity(x, ...)) + smooth_log_periodicity(x, ...)) add_model("bowl_18_min_freq_dist", "Bowling et al. (2018)", "Interference", - "bowl18", + "incon", consonance = TRUE, spectrum_sensitive = FALSE, continuous_pitch = TRUE, f = function(x, num_harmonics, roll_off, ...) - bowl18::bowl18_min_freq_dist(x, ...)) + bowl18_min_freq_dist(x, ...)) add_model("huron_94_dyadic", "Huron (1994)", @@ -155,101 +155,101 @@ add_model("huron_94_dyadic", add_model("hutch_78_roughness", "Hutchinson & Knopoff (1978)", "Interference", - "dycon", + "incon", consonance = FALSE, spectrum_sensitive = TRUE, continuous_pitch = TRUE, f = function(x, num_harmonics, roll_off, ...) - dycon::roughness_hutch(x, - num_harmonics = num_harmonics, - roll_off = roll_off, - ...)) + roughness_hutch(x, + num_harmonics = num_harmonics, + roll_off = roll_off, + ...)) add_model("parn_94_pure", "Parncutt & Strasburger (1994)", "Interference", - "parn94", + "incon", consonance = TRUE, spectrum_sensitive = TRUE, continuous_pitch = FALSE, f = function(x, num_harmonics, roll_off, ...) - parn94::pure_sonor(x, - num_harmonics = num_harmonics, - roll_off = roll_off, - ...)) + pure_sonor(x, + num_harmonics = num_harmonics, + roll_off = roll_off, + ...)) add_model("seth_93_roughness", "Sethares (1993)", "Interference", - "dycon", + "incon", consonance = FALSE, spectrum_sensitive = TRUE, continuous_pitch = TRUE, f = function(x, num_harmonics, roll_off, ...) - dycon::roughness_seth(x, - num_harmonics = num_harmonics, - roll_off = roll_off, - ...)) + roughness_seth(x, + num_harmonics = num_harmonics, + roll_off = roll_off, + ...)) add_model("vass_01_roughness", "Vassilakis (2001)", "Interference", - "dycon", + "incon", consonance = FALSE, spectrum_sensitive = TRUE, continuous_pitch = TRUE, f = function(x, num_harmonics, roll_off, ...) - dycon::roughness_vass(x, - num_harmonics = num_harmonics, - roll_off = roll_off, - ...)) + roughness_vass(x, + num_harmonics = num_harmonics, + roll_off = roll_off, + ...)) add_model("wang_13_roughness", "Wang et al. (2013)", "Interference", - "wang13", + "incon", consonance = FALSE, spectrum_sensitive = TRUE, continuous_pitch = TRUE, f = function(x, num_harmonics, roll_off, ...) - wang13::roughness_wang(x, - num_harmonics = num_harmonics, - roll_off = roll_off, - msg = NULL, - ...)) + roughness_wang(x, + num_harmonics = num_harmonics, + roll_off = roll_off, + msg = NULL, + ...)) add_model("jl_12_tonal", "Johnson-Laird et al. (2012)", "Culture", - "jl12", + "incon", consonance = FALSE, spectrum_sensitive = FALSE, continuous_pitch = FALSE, f = function(x, num_harmonics, roll_off, ...) - jl12::jl_tonal_dissonance(x, ...)) + jl_tonal_dissonance(x, ...)) add_model("har_19_corpus", "Harrison & Pearce (2019)", "Culture", - "corpdiss", + "incon", consonance = FALSE, spectrum_sensitive = FALSE, continuous_pitch = FALSE, f = function(x, num_harmonics, roll_off, ...) - corpdiss::corpus_dissonance(x, ...)) + corpus_dissonance(x, ...)) add_model("parn_94_mult", "Parncutt & Strasburger (1994)", "Numerosity", - "parn94", + "incon", consonance = TRUE, spectrum_sensitive = TRUE, continuous_pitch = FALSE, f = function(x, num_harmonics, roll_off, ...) - parn94::multiplicity(x, - num_harmonics = num_harmonics, - roll_off = roll_off, - ...)) + multiplicity(x, + num_harmonics = num_harmonics, + roll_off = roll_off, + ...)) add_model("har_19_composite", "Harrison & Pearce (2019)", diff --git a/R/top-level.R b/R/top-level.R index bece0b4..e80e1fd 100644 --- a/R/top-level.R +++ b/R/top-level.R @@ -58,58 +58,58 @@ #' @details #' The following models are available: #' * `gill_09_harmonicity`: -#' the harmonicity model of \insertCite{Gill2009;textual}{bowl18} -#' (see \code{bowl18::\link[bowl18]{gill09_harmonicity}}). +#' the harmonicity model of \insertCite{Gill2009;textual}{incon} +#' (see \code{incon::\link[incon]{gill09_harmonicity}}). #' * `har_18_harmonicity`: -#' the harmonicity model of \insertCite{Harrison2018;textual}{har18} -#' (see \code{har18::\link[har18]{pc_harmonicity}}). +#' the harmonicity model of \insertCite{Harrison2018;textual}{incon} +#' (see \code{incon::\link[incon]{pc_harmonicity}}). #' * `milne_13_harmonicity`: -#' the harmonicity model of \insertCite{Milne2013;textual}{har18} -#' (see \code{har18::\link[har18]{pc_harmonicity}}). +#' the harmonicity model of \insertCite{Milne2013;textual}{incon} +#' (see \code{incon::\link[incon]{pc_harmonicity}}). #' * `parn_88_root_ambig`: -#' the root ambiguity model of \insertCite{Parncutt1988;textual}{parn88} -#' (see \code{parn88::\link[parn88]{root_ambiguity}}). +#' the root ambiguity model of \insertCite{Parncutt1988;textual}{incon} +#' (see \code{incon::\link[incon]{root_ambiguity}}). #' * `parn_94_complex`: -#' the complex sonorousness feature of \insertCite{Parncutt1994;textual}{parn94} -#' (see \code{parn94::\link[parn94]{complex_sonor}}). +#' the complex sonorousness feature of \insertCite{Parncutt1994;textual}{incon} +#' (see \code{incon::\link[incon]{complex_sonor}}). #' * `stolz_15_periodicity`: #' smoothed logarithmic periodicity, -#' after \insertCite{Stolzenburg2015;textual}{stolz15} -#' (see \code{stolz15::\link[stolz15]{smooth_log_periodicity}}). +#' after \insertCite{Stolzenburg2015;textual}{incon} +#' (see \code{incon::\link[incon]{smooth_log_periodicity}}). #' * `bowl_18_min_freq_dist`: #' the minimum frequency distance feature of -#' \insertCite{Bowling2018;textual}{bowl18} -#' (see \code{bowl18::\link[bowl18]{bowl18_min_freq_dist}}). +#' \insertCite{Bowling2018;textual}{incon} +#' (see \code{incon::\link[incon]{bowl18_min_freq_dist}}). #' * `huron_94_dyadic`: #' aggregate dyadic consonance, after \insertCite{Huron1994;textual}{incon}. #' * `hutch_78_roughness`: -#' the roughness model of \insertCite{Hutchinson1978;textual}{dycon} -#' (see \code{dycon::\link[dycon]{roughness_hutch}}). +#' the roughness model of \insertCite{Hutchinson1978;textual}{incon} +#' (see \code{incon::\link[incon]{roughness_hutch}}). #' * `parn_94_pure`: -#' the complex sonorousness feature of \insertCite{Parncutt1994;textual}{parn94} -#' (see \code{parn94::\link[parn94]{pure_sonor}}). +#' the complex sonorousness feature of \insertCite{Parncutt1994;textual}{incon} +#' (see \code{incon::\link[incon]{pure_sonor}}). #' * `seth_93_roughness`: -#' the roughness model of \insertCite{Sethares1993;textual}{dycon} -#' (see \code{dycon::\link[dycon]{roughness_seth}}). +#' the roughness model of \insertCite{Sethares1993;textual}{incon} +#' (see \code{incon::\link[incon]{roughness_seth}}). #' * `vass_01_roughness`: -#' the roughness model of \insertCite{Vassilakis2001;textual}{dycon} -#' (see \code{dycon::\link[dycon]{roughness_vass}}). +#' the roughness model of \insertCite{Vassilakis2001;textual}{incon} +#' (see \code{incon::\link[incon]{roughness_vass}}). #' * `wang_13_roughness`: -#' the roughness model of \insertCite{Wang2013;textual}{wang13} -#' (see \code{wang13::\link[wang13]{roughness_wang}}). +#' the roughness model of \insertCite{Wang2013;textual}{incon} +#' (see \code{incon::\link[incon]{roughness_wang}}). #' * `jl_12_tonal`: -#' the tonal dissonance model of \insertCite{Johnson-Laird2012;textual}{jl12} -#' (see \code{jl12::\link[jl12]{jl_tonal_dissonance}}). +#' the tonal dissonance model of \insertCite{Johnson-Laird2012;textual}{incon} +#' (see \code{incon::\link[incon]{jl_tonal_dissonance}}). #' * `har_19_corpus`: #' a corpus-based model of cultural familiarity #' \insertCite{Harrison2019}{incon} -#' (see \code{corpdiss::\link[corpdiss]{corpus_dissonance}}). +#' (see \code{incon::\link[incon]{corpus_dissonance}}). #' * `parn_94_mult`: -#' the multiplicity feature of \insertCite{Parncutt1994;textual}{parn94} -#' (see \code{parn94::\link[parn94]{multiplicity}}). +#' the multiplicity feature of \insertCite{Parncutt1994;textual}{incon} +#' (see \code{incon::\link[incon]{multiplicity}}). #' * `har_19_composite`: -#' a model combining interference \insertCite{Hutchinson1978}{dycon}, -#' periodicity/harmonicity \insertCite{Harrison2018}{har18}, +#' a model combining interference \insertCite{Hutchinson1978}{incon}, +#' periodicity/harmonicity \insertCite{Harrison2018}{incon}, #' and cultural familiarity, #' as introduced by \insertCite{Harrison2019;textual}{incon}. #' @references diff --git a/README.Rmd b/README.Rmd index 00de05b..bf8f2ad 100644 --- a/README.Rmd +++ b/README.Rmd @@ -31,10 +31,8 @@ models of simultaneous consonance perception. ## Citation -Harrison, P. M. C., & Pearce, M. T. (2019). -Instantaneous consonance in the perception and composition of Western music. -*PsyArXiv*. -https://doi.org/10.31234/osf.io/6jsug +Harrison, P. M. C., & Pearce, M. T. (2020). Simultaneous consonance in music perception and composition. +*Psychological Review*, *127*(2), 216–244. ## Installation @@ -46,6 +44,27 @@ if (!require(devtools)) install.packages("devtools") devtools::install_github("pmcharrison/incon") ``` +To run the Jupyter notebook, you need to download this code repository. +You then need to install Jupyter Notebook (see https://jupyter.org/install): + +``` +pip install notebook +``` + +Then open R, install `IRkernel`, then run `installspec`: + +```r +install.packages("IRkernel") +IRkernel::installspec() +``` + +Then from a new terminal, open this notebook: + +``` +jupyter notebook Demo.ipynb +``` + + ## Usage The primary function is `incon`, @@ -86,39 +105,12 @@ incon::incon_models %>% See `?incon` for more details. -## Packages - -The functionality of `incon` is split between several low-level R packages, -listed below. - -```{r, results = "asis", echo = FALSE} -tribble( - ~ Package, ~ DOI, - "bowl18", "10.5281/zenodo.2545741", - "corpdiss", "10.5281/zenodo.2545748", - "dycon", "10.5281/zenodo.2545750", - "har18", "10.5281/zenodo.2545752", - "hcorp", "10.5281/zenodo.2545754", - "hrep", "10.5281/zenodo.2545770", - "jl12", "10.5281/zenodo.2545756", - "parn88", "10.5281/zenodo.1491909", - "parn94", "10.5281/zenodo.2545759", - "stolz15", "10.5281/zenodo.2545762", - "wang13", "10.5281/zenodo.2545764", -) %>% - mutate(GitHub = paste("https://github.com/pmcharrison/", - Package, - sep = ""), - DOI = sprintf("[%s](https://doi.org/%s)", DOI, DOI)) %>% - kable -``` - ## Spectral representations Certain `incon` models can be applied to full frequency spectra rather than just symbolically notated chords. One example is the set of interference models provided in the `dycon` package. In order to run such models on full frequency -spectra one must call `hrep` and `dycon` functions explicitly, as in the +spectra one must call lower-level functions explicitly, as in the following example, which computes the roughness of a chord using the Hutchinson-Knopoff dissonance model: @@ -129,7 +121,7 @@ spectrum <- amplitude = c(1, 0.7, 0.9, 0.6) )) -dycon::roughness_hutch(spectrum) +roughness_hutch(spectrum) ``` ## References diff --git a/README.md b/README.md index 89b133a..a93850f 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,9 @@ simultaneous consonance perception. ## Citation -Harrison, P. M. C., & Pearce, M. T. (2020). Simultaneous consonance in music perception and composition. *Psychological Review, 127*(2), 216-244. http://dx.doi.org/10.1037/rev0000169 +Harrison, P. M. C., & Pearce, M. T. (2019). Instantaneous consonance in +the perception and composition of Western music. *PsyArXiv*. + ## Installation @@ -25,6 +27,23 @@ if (!require(devtools)) install.packages("devtools") devtools::install_github("pmcharrison/incon") ``` +To run the Jupyter notebook, you need to download this code repository. +You then need to install Jupyter Notebook (see +): + + pip install notebook + +Then open R, install `IRkernel`, then run `installspec`: + +``` r +install.packages("IRkernel") +IRkernel::installspec() +``` + +Then from a new terminal, open this notebook: + + jupyter notebook Demo.ipynb + ## Usage The primary function is `incon`, which applies consonance models to an @@ -56,60 +75,38 @@ documentation, `?incon`, for further details. ## Models -Currently the following models are -implemented: - -| Label | Citation | Class | Package | -| :------------------------ | :---------------------------- | :---------------------- | :------- | -| gill\_09\_harmonicity | Gill & Purves (2009) | Periodicity/harmonicity | bowl18 | -| har\_18\_harmonicity | Harrison & Pearce (2018) | Periodicity/harmonicity | har18 | -| milne\_13\_harmonicity | Milne (2013) | Periodicity/harmonicity | har18 | -| parn\_88\_root\_ambig | Parncutt (1988) | Periodicity/harmonicity | parn88 | -| parn\_94\_complex | Parncutt & Strasburger (1994) | Periodicity/harmonicity | parn94 | -| stolz\_15\_periodicity | Stolzenburg (2015) | Periodicity/harmonicity | stolz15 | -| bowl\_18\_min\_freq\_dist | Bowling et al. (2018) | Interference | bowl18 | -| huron\_94\_dyadic | Huron (1994) | Interference | incon | -| hutch\_78\_roughness | Hutchinson & Knopoff (1978) | Interference | dycon | -| parn\_94\_pure | Parncutt & Strasburger (1994) | Interference | parn94 | -| seth\_93\_roughness | Sethares (1993) | Interference | dycon | -| vass\_01\_roughness | Vassilakis (2001) | Interference | dycon | -| wang\_13\_roughness | Wang et al. (2013) | Interference | wang13 | -| jl\_12\_tonal | Johnson-Laird et al. (2012) | Culture | jl12 | -| har\_19\_corpus | Harrison & Pearce (2019) | Culture | corpdiss | -| parn\_94\_mult | Parncutt & Strasburger (1994) | Numerosity | parn94 | -| har\_19\_composite | Harrison & Pearce (2019) | Composite | incon | +Currently the following models are implemented: + +| Label | Citation | Class | Package | +|:----------------------|:------------------------------|:------------------------|:--------| +| gill_09_harmonicity | Gill & Purves (2009) | Periodicity/harmonicity | incon | +| har_18_harmonicity | Harrison & Pearce (2018) | Periodicity/harmonicity | incon | +| milne_13_harmonicity | Milne (2013) | Periodicity/harmonicity | incon | +| parn_88_root_ambig | Parncutt (1988) | Periodicity/harmonicity | incon | +| parn_94_complex | Parncutt & Strasburger (1994) | Periodicity/harmonicity | incon | +| stolz_15_periodicity | Stolzenburg (2015) | Periodicity/harmonicity | incon | +| bowl_18_min_freq_dist | Bowling et al. (2018) | Interference | incon | +| huron_94_dyadic | Huron (1994) | Interference | incon | +| hutch_78_roughness | Hutchinson & Knopoff (1978) | Interference | incon | +| parn_94_pure | Parncutt & Strasburger (1994) | Interference | incon | +| seth_93_roughness | Sethares (1993) | Interference | incon | +| vass_01_roughness | Vassilakis (2001) | Interference | incon | +| wang_13_roughness | Wang et al. (2013) | Interference | incon | +| jl_12_tonal | Johnson-Laird et al. (2012) | Culture | incon | +| har_19_corpus | Harrison & Pearce (2019) | Culture | incon | +| parn_94_mult | Parncutt & Strasburger (1994) | Numerosity | incon | +| har_19_composite | Harrison & Pearce (2019) | Composite | incon | See `?incon` for more details. -## Packages - -The functionality of `incon` is split between several low-level R -packages, listed -below. - -| Package | DOI | GitHub | -| :------- | :--------------------------------------------------------------- | :---------------------------------------- | -| bowl18 | [10.5281/zenodo.2545741](https://doi.org/10.5281/zenodo.2545741) | | -| corpdiss | [10.5281/zenodo.2545748](https://doi.org/10.5281/zenodo.2545748) | | -| dycon | [10.5281/zenodo.2545750](https://doi.org/10.5281/zenodo.2545750) | | -| har18 | [10.5281/zenodo.2545752](https://doi.org/10.5281/zenodo.2545752) | | -| hcorp | [10.5281/zenodo.2545754](https://doi.org/10.5281/zenodo.2545754) | | -| hrep | [10.5281/zenodo.2545770](https://doi.org/10.5281/zenodo.2545770) | | -| jl12 | [10.5281/zenodo.2545756](https://doi.org/10.5281/zenodo.2545756) | | -| parn88 | [10.5281/zenodo.1491909](https://doi.org/10.5281/zenodo.1491909) | | -| parn94 | [10.5281/zenodo.2545759](https://doi.org/10.5281/zenodo.2545759) | | -| stolz15 | [10.5281/zenodo.2545762](https://doi.org/10.5281/zenodo.2545762) | | -| wang13 | [10.5281/zenodo.2545764](https://doi.org/10.5281/zenodo.2545764) | | - - ## Spectral representations -Certain `incon` models can be applied to full frequency spectra rather than just -symbolically notated chords. One example is the set of interference models -provided in the `dycon` package. In order to run such models on full frequency -spectra one must call `hrep` and `dycon` functions explicitly, as in the -following example, which computes the roughness of a chord using the -Hutchinson-Knopoff dissonance model: +Certain `incon` models can be applied to full frequency spectra rather +than just symbolically notated chords. One example is the set of +interference models provided in the `dycon` package. In order to run +such models on full frequency spectra one must call lower-level +functions explicitly, as in the following example, which computes the +roughness of a chord using the Hutchinson-Knopoff dissonance model: ``` r spectrum <- @@ -118,7 +115,7 @@ spectrum <- amplitude = c(1, 0.7, 0.9, 0.6) )) -dycon::roughness_hutch(spectrum) +roughness_hutch(spectrum) ``` ## References diff --git a/data-raw/by-pc-chord.R b/data-raw/by-pc-chord.R new file mode 100644 index 0000000..22b0135 --- /dev/null +++ b/data-raw/by-pc-chord.R @@ -0,0 +1,13 @@ +n <- hrep::alphabet_size("pc_chord") +chords <- hrep::list_chords("pc_chord") +root_by_pc_chord <- integer(n) +pb <- utils::txtProgressBar(max = n, style = 3) + +for (i in seq_len(n)) { + root_by_pc_chord[i] <- root(chords[[i]]) + utils::setTxtProgressBar(pb, i) +} + +close(pb) + +use_data(root_by_pc_chord, overwrite = TRUE) diff --git a/data-raw/make-tables.R b/data-raw/make-tables.R new file mode 100644 index 0000000..6dcfb85 --- /dev/null +++ b/data-raw/make-tables.R @@ -0,0 +1,23 @@ +# Generates the corpus dissonance tables that are +# stored along with this package. + +library(incon) + +corpora <- list(popular_1 = hcorp::popular_1) +type <- "pc_chord_type" + +unlink("data", recursive = TRUE) +dir.create("data") + +for (i in seq_along(corpora)) { + corpus <- corpora[[i]] + corpus_label <- names(corpora)[[i]] + data_label <- paste0(corpus_label, "_", type) + + env <- new.env() + env[[data_label]] <- corpus_dissonance_table(corpus, type) + + save(list = data_label, + envir = env, + file = file.path("data", paste0(data_label, ".rda"))) +} diff --git a/data/popular_1_pc_chord_type.rda b/data/popular_1_pc_chord_type.rda new file mode 100644 index 0000000..0526e0a Binary files /dev/null and b/data/popular_1_pc_chord_type.rda differ diff --git a/data/root_by_pc_chord.rda b/data/root_by_pc_chord.rda new file mode 100644 index 0000000..271a1f3 Binary files /dev/null and b/data/root_by_pc_chord.rda differ diff --git a/inst/REFERENCES.bib b/inst/REFERENCES.bib index 2c65852..2f34f0f 100644 --- a/inst/REFERENCES.bib +++ b/inst/REFERENCES.bib @@ -16,3 +16,225 @@ @article{Harrison2019 title = {{Instantaneous consonance in the perception and composition of Western music}}, year = {2019} } + +@article{Wang2013, +abstract = {In this paper, a roughness model, which is based on human auditory perception (HAP) and known as HAP-RM, is developed for the sound quality evaluation (SQE) of vehicle noise. First, the interior noise signals are measured for a sample vehicle and prepared for roughness modelling. The HAP-RM model is based on the process of sound transfer and perception in the human auditory system by combining the structural filtering function and nonlinear perception characteristics of the ear. The HAP-RM model is applied to the measured vehicle interior noise signals by considering the factors that affect hearing, such as the modulation and carrier frequencies, the time and frequency maskings and the correlations of the critical bands. The HAP-RM model is validated by jury tests. An anchor-scaled scoring method (ASM) is used for subjective evaluations in the jury tests. The verification results show that the novel developed model can accurately calculate vehicle noise roughness below 0.6 asper. Further investigation shows that the total roughness of the vehicle interior noise can mainly be attributed to frequency components below 12 Bark. The time masking effects of the modelling procedure enable the application of the HAP-RM model to stationary and nonstationary vehicle noise signals and the SQE of other sound-related signals in engineering problems.}, +author = {Wang, Y.S. and Shen, G.Q. and Guo, H. and Tang, X.L. and Hamade, T.}, +doi = {10.1016/j.jsv.2013.02.030}, +file = {:Users/peter/Dropbox/Academic/literature/Wang et al/Wang et al. - 2013 - Roughness modelling based on human auditory perception for sound quality evaluation of vehicle interior noise.pdf:pdf}, +journal = {Journal of Sound and Vibration}, +mendeley-groups = {Dissonance models/Roughness}, +number = {16}, +pages = {3893--3904}, +publisher = {Elsevier}, +title = {{Roughness modelling based on human auditory perception for sound quality evaluation of vehicle interior noise}}, +volume = {332}, +year = {2013} +} + +@inproceedings{Harrison2018, +address = {Paris, France}, +author = {Harrison, Peter M. C. and Pearce, Marcus T.}, +booktitle = {Proceedings of the 19th International Society for Music Information Retrieval Conference}, +pages = {160--167}, +title = {{An energy-based generative sequence model for testing sensory theories of Western harmony}}, +year = {2018} +} + +@phdthesis{Milne2013, +address = {Milton Keynes, England}, +author = {Milne, Andrew J.}, +mendeley-groups = {Harmony,Harmony/Psychoacoustic interpretation}, +school = {The Open University}, +title = {{A computational model of the cognition of tonality}}, +year = {2013} +} + +@article{Milne2016, +author = {Milne, Andrew J. and Holland, Simon}, +doi = {10.1080/17459737.2016.1152517}, +journal = {Journal of Mathematics and Music}, +number = {1}, +pages = {59--85}, +title = {{Empirically testing Tonnetz, voice-leading, and spectral models of perceived triadic distance}}, +volume = {10}, +year = {2016} +} + +@article{Johnson-Laird2012, +author = {Johnson-Laird, Phil N. and Kang, Olivia E. and Leong, Yuan Chang}, +file = {:Users/peter/Dropbox/Academic/literature/Johnson-Laird, Kang, Leong/Johnson-Laird, Kang, Leong - 2012 - On musical dissonance.pdf:pdf}, +journal = {Music Perception}, +keywords = {consonance,dissonance,harmony,sensory}, +number = {1}, +pages = {19--35}, +title = {{On musical dissonance}}, +volume = {30}, +year = {2012} +} + +@article{Parncutt2006, +author = {Parncutt, Richard}, +file = {:Users/peter/Dropbox/Academic/literature/Parncutt/Parncutt - 2006 - Commentary on Cook {\&} Fujisawa's The Psychophysics of Harmony Perception Harmony is a Three-Tone Phenomenon.pdf:pdf}, +journal = {Empirical Musicology Review}, +number = {4}, +pages = {204--209}, +title = {{Commentary on Cook & Fujisawa's "The Psychophysics of Harmony Perception: Harmony is a Three-Tone Phenomenon"}}, +volume = {1}, +year = {2006} +} + +@article{Parncutt1988, +author = {Parncutt, Richard}, +file = {:Users/peter/Dropbox/Academic/literature/Parncutt/Parncutt - 1988 - Revision of Terhardt's psychoacoustical model of the root(s) of a musical chord(2).pdf:pdf}, +journal = {Music Perception}, +mendeley-groups = {Harmony,Consonance}, +number = {1}, +pages = {65--93}, +title = {{Revision of Terhardt's psychoacoustical model of the root(s) of a musical chord}}, +volume = {6}, +year = {1988} +} + +@article{Parncutt1994, +author = {Parncutt, Richard and Strasburger, Hans}, +journal = {Perspectives of New Music}, +number = {2}, +pages = {88--129}, +title = {{Applying psychoacoustics in composition: "Harmonic" progressions of "nonharmonic" sonorities}}, +volume = {32}, +year = {1994} +} + +@article{Stolzenburg2015, +abstract = {ISSN: 1745-9737 (Print) 1745-9745 (Online) Journal homepage: http://www.tandfonline.com/loi/tmam20 The perception of consonance/dissonance of musical harmonies is strongly correlated with their peri-odicity. This is shown in this article by consistently applying recent results from psychophysics and neuroacoustics, namely that the just noticeable difference between two pitches for humans is about 1{\%} for the musically important low frequency range and that periodicities of complex chords can be detected in the human brain. Based thereon, the concepts of relative and logarithmic periodicity with smooth-ing are introduced as powerful measures of harmoniousness. The presented results correlate significantly with empirical investigations on the perception of chords. Even for scales, plausible results are obtained. For example, all classical church modes appear in the front ranks of all theoretically possible seven-tone scales.}, +archivePrefix = {arXiv}, +arxivId = {1306.6458}, +author = {Stolzenburg, F.}, +doi = {10.1080/17459737.2015.1033024}, +eprint = {1306.6458}, +journal = {Journal of Mathematics and Music}, +number = {3}, +pages = {215--238}, +title = {Harmony perception by periodicity detection}, +volume = {9}, +year = {2015} +} + +@article{Mashinter2006, +author = {Mashinter, Keith}, +journal = {Empirical Musicology Review}, +number = {2}, +pages = {65--84}, +title = {{Calculating sensory dissonance: Some discrepancies arising from the models of Kameoka & Kuriyagawa, and Hutchinson & Knopoff}}, +volume = {1}, +year = {2006} +} + +@article{Hutchinson1978, +author = {Hutchinson, William and Knopoff, Leon}, +doi = {10.1080/09298217808570246}, +journal = {Journal of New Music Research}, +number = {1}, +pages = {1--29}, +title = {{The acoustic component of Western consonance}}, +volume = {7}, +year = {1978} +} + +@article{Bigand1996, +abstract = {This study investigates the effect of four variables (tonal hierarchies, sensory chordal consonance, horizontal motion, and musical training) on perceived musical tension. Participants were asked to evaluate the tension created by a chord X in sequences of three chords [C major--{\textgreater}X--{\textgreater}C major] in a C major context key. The X chords could be major or minor triads major-minor seventh, or minor seventh chords built on the 12 notes of the chromatic scale. The data were compared with Krumhansl's (1990) harmonic hierarchy and with predictions of Lerdahl's (1988) cognitive theory, Hutchinson and Knopoff's (1978) and Parncutt's (1989) sensory-psychoacoustical theories, and the model of horizontal motion defined in the paper. As a main outcome, it appears that judgments of tension arose from a convergence of several cognitive and psychoacoustics influences, whose relative importance varies, depending on musical training.}, +author = {Bigand, E and Parncutt, R and Lerdahl, F}, +doi = {10.3758/BF03205482}, +journal = {Perception & psychophysics}, +number = {1}, +pages = {124--141}, +title = {{Perception of musical tension in short chord sequences: The influence of harmonic function, sensory dissonance, horizontal motion, and musical training}}, +volume = {58}, +year = {1996} +} + +@article{Plomp1965, +abstract = {Firstly, theories are reviewed on the explanation of tonal consonance as the singular nature of tone intervals with frequency ratios corresponding with small integer numbers. An evaluation of these explanations in the light of some experimental studies supports the hypothesis, as promoted by von Helmholtz, that the difference between consonant and dissonant intervals is related to beats of adjacent partials. This relation was studied more fully by experiments in which subjects had to judge simple?tone intervals as a function of test frequency and interval width. The results may be considered as a modification of von Helmholtz's conception and indicate that, as a function of frequency, the transition range between consonant and dissonant intervals is related to critical bandwidth. Simple?tone intervals are evaluated as consonant for frequency differences exceeding this bandwidth. whereas the most dissonant intervals correspond with frequency differences of about a quarter of this bandwidth. On the base of these results, some properties of consonant intervals consisting of complex tones are explained. To answer the question whether critical bandwidth also plays a r{\^{o}}le in music, the chords of two compositions (parts of a trio sonata of J. S. Bach and of a string quartet of A. Dvo?{\'{a}}k) were analyzed by computing interval distributions as a function of frequency and number of harmonics taken into account. The results strongly suggest that, indeed, critical bandwidth plays an important r{\^{o}}le in music: for a number of harmonics representative for musical instruments, the ?density? of simultaneous partials alters as a function of frequency in the same way as critical bandwidth does.}, +author = {Plomp, R. and Levelt, W. J. M.}, +doi = {http://dx.doi.org/10.1121/1.1909741}, +journal = {The Journal of the Acoustical Society of America}, +number = {4}, +pages = {548--560}, +title = {{Tonal consonance and critical bandwidth}}, +volume = {38}, +year = {1965} +} + +@inproceedings{Villegas2010, +address = {London, England}, +author = {Villegas, Julian and Cohen, Michael and Wilson, Ian and Martens, William}, +booktitle = {128th Audio Engineering Society Convention}, +keywords = {intonation,musical consonance,pleasantness,preference,roughness,tuning}, +pages = {1--11}, +title = {Influence of Psychoacoustic Roughness on Musical Intonation Preference}, +url = {http://www.aes.org/e-lib/browse.cfm?elib=15314}, +year = {2010} +} + +@phdthesis{Vassilakis2001, +address = {Los Angeles, CA}, +author = {Vassilakis, P. N.}, +school = {UCLA}, +title = {Perceptual and Physical Properties of Amplitude Fluctuation and their Musical Significance}, +year = {2001} +} + +@article{Sethares1993, +author = {Sethares, William A.}, +journal = {The Journal of the Acoustical Society of America}, +number = {3}, +pages = {1218--1228}, +title = {Local consonance and the relationship between timbre and scale}, +volume = {94}, +year = {1993} +} + +@inproceedings{Weisser2013, +address = {Amsterdam, The Netherlands}, +author = {Weisser, St{\'{e}}phanie and Lartillot, Olivier}, +booktitle = {Proceedings of the Third International Workshop on Folk Music Analysis}, +file = {:Users/peter/Dropbox/Academic/literature/Weisser, Lartillot/Weisser, Lartillot - 2013 - Investigating non-Western musical timbre A need for joint approaches.pdf:pdf}, +pages = {33--39}, +title = {{Investigating non-Western musical timbre: A need for joint approaches}}, +year = {2013} +} + +@book{Sethares2005, +address = {London, UK}, +author = {Sethares, William A.}, +publisher = {Springer}, +title = {{Tuning, timbre, spectrum, scale}}, +year = {2005} +} + +@article{Bowling2018, +author = {Bowling, Daniel L and Purves, Dale and Gill, Kamraan Z}, +doi = {10.1073/pnas.1713206115}, +journal = {Proceedings of the National Academy of Sciences}, +number = {1}, +pages = {216--221}, +title = {{Vocal similarity predicts the relative attraction of musical chords}}, +volume = {115}, +year = {2018} +} + +@article{Gill2009, +abstract = {Scales are collections of tones that divide octaves into specific intervals used to create music. Since humans can distinguish about 240 different pitches over an octave in the mid-range of hearing, in principle a very large number of tone combinations could have been used for this purpose. Nonetheless, compositions in Western classical, folk and popular music as well as in many other musical traditions are based on a relatively small number of scales that typically comprise only five to seven tones. Why humans employ only a few of the enormous number of possible tone combinations to create music is not known. Here we show that the component intervals of the most widely used scales throughout history and across cultures are those with the greatest overall spectral similarity to a harmonic series. These findings suggest that humans prefer tone combinations that reflect the spectral characteristics of conspecific vocalizations. The analysis also highlights the spectral similarity among the scales used by different cultures.}, +author = {Gill, Kamraan Z. and Purves, Dale}, +doi = {10.1371/journal.pone.0008144}, +journal = {PLoS ONE}, +mendeley-groups = {Consonance}, +number = {12}, +pages = {1--9}, +pmid = {19997506}, +title = {{A biological rationale for musical scales}}, +volume = {4}, +year = {2009} +} + diff --git a/inst/bigand1996/bigand-1996.R b/inst/bigand1996/bigand-1996.R new file mode 100644 index 0000000..012c13a --- /dev/null +++ b/inst/bigand1996/bigand-1996.R @@ -0,0 +1,152 @@ +# This script compiles data for Bigand et al. (1996), +# which can subsequently be used for regression testing. + +# Bigand, E., Parncutt, R., & Lerdahl, F. (1996). +# Perception of musical tension in short chord sequences: +# The influence of harmonic function, sensory dissonance, horizontal motion, +# and musical training. Perception & Psychophysics, 58(1), 124–141. +# https://doi.org/10.3758/BF03205482 + +library(magrittr) + +convert_chord_type_to_pc_set <- function(chord_type) { + assertthat::assert_that( + assertthat::is.scalar(chord_type), + is.character(chord_type) || is.factor(chord_type) + ) + if (chord_type == "diminished") { + c(0, 3, 6) + } else if (chord_type == "major") { + c(0, 4, 7) + } else if (chord_type == "minor") { + c(0, 3, 7) + } else if (chord_type == "minor_seventh") { + c(0, 3, 7, 10) + } else if (chord_type == "revoiced") { + c(0, 4, 7) + } else if (chord_type == "seventh") { + c(0, 4, 7, 10) + } else stop("Unrecognised chord type") +} + +deduce_chord_pitches <- function( + pc_set, + bass_interval_size, tenor_interval_size, + alto_interval_size, soprano_interval_size, + previous_chord_pitches +) { + # Known constraints: + # - bass always rises + # - soprano always falls + bass_pitch <- previous_chord_pitches$bass + bass_interval_size + soprano_pitch <- previous_chord_pitches$soprano - soprano_interval_size + bass_pc <- bass_pitch %% 12 + soprano_pc <- soprano_pitch %% 12 + # Check all variants of alto/tenor movement and work out which satisfy the constraints + df <- expand.grid(tenor_ascends = c(FALSE, TRUE), + alto_ascends = c(FALSE, TRUE)) + df$tenor_pitch <- ifelse(df$tenor_ascends, + previous_chord_pitches$tenor + tenor_interval_size, + previous_chord_pitches$tenor - tenor_interval_size) + df$alto_pitch <- ifelse(df$alto_ascends, + previous_chord_pitches$alto + alto_interval_size, + previous_chord_pitches$alto - alto_interval_size) + df$tenor_pc <- df$tenor_pitch %% 12 + df$alto_pc <- df$alto_pitch %% 12 + df$num_repeated_pitches <- mapply( + function(tenor_pitch, alto_pitch) { + 4 - length(unique(c(bass_pitch, tenor_pitch, alto_pitch, soprano_pitch))) + }, df$tenor_pitch, df$alto_pitch + ) + df$num_unique_pcs <- mapply( + function(tenor_pc, alto_pc) { + length(unique(c(bass_pc, tenor_pc, alto_pc, soprano_pc))) + }, + df$tenor_pc, df$alto_pc + ) + df$valid <- mapply( + function(tenor_pc, alto_pc, tenor_pitch, alto_pitch) { + # Must not contain extraneous pitch classes + all( + c(bass_pc, tenor_pc, alto_pc, soprano_pc) %in% pc_set + ) && + # Don't allow part crossing + bass_pitch <= tenor_pitch && + tenor_pitch <= alto_pitch && + alto_pitch <= soprano_pitch && + # Don't allow the third to be doubled. + # The bass is always the root, so the third is 3 or 4 semitones + # above the bass + sum( + c(bass_pc, tenor_pc, alto_pc, soprano_pc) %in% + c((bass_pc + 3) %% 12, (bass_pitch + 4) %% 12) + ) < 2 && + # The seventh cannot be omitted in seventh chords + ( + !((bass_pc + 10) %% 12) %in% pc_set || + ((bass_pc + 10) %% 12) %in% c(bass_pc, tenor_pc, alto_pc, soprano_pc) + ) + }, + df$tenor_pc, df$alto_pc, df$tenor_pitch, df$alto_pitch + ) + # Solution must be valid + df_chosen <- df[df$valid, + setdiff(names(df), c("tenor_ascends", "alto_ascends"))] %>% + unique %>% + # and minimise the number of repeated pitches + (function(df) df[df$num_repeated_pitches == min(df$num_repeated_pitches), ]) %>% + # and maximise the number of unique pitch classes + (function(df) df[df$num_unique_pcs == max(df$num_unique_pcs), ]) + if (nrow(df_chosen) == 0) { + message("No solutions found") + browser() + } else if (nrow(df_chosen) > 1) { + message("Multiple solutions sound") + browser() + } + c(bass_pitch, df_chosen$tenor_pitch, + df_chosen$alto_pitch, soprano_pitch) +} + +# Actions #### + +bigand_1996 <- read.csv("inst/bigand1996/raw/bigand-1996-data.csv", stringsAsFactors = FALSE) %>% + # Correct mistakes + ## Incorrect tenor int. size - fixed using Fig. 1 + (function(df) { + df$tenor_interval_size[df$label == "C#7"] <- 4 + df$tenor_interval_size[df$label == "c#7"] <- 4 + df$tenor_interval_size[df$label == "ab7"] <- 4 + df + }) + +bigand_1996_reference_chord <- read.csv("inst/bigand1996/raw/bigand-1996-reference-chord.csv", + stringsAsFactors = FALSE) %>% as.list + +bigand_1996$root_pc <- bigand_1996$bass_interval_size # because all chords were root position +bigand_1996$pc_set <- mapply(function(root_pc, chord_type) { + convert_chord_type_to_pc_set(chord_type) %>% + add(root_pc) %>% + mod(., 12) %>% + sort +}, bigand_1996$root_pc, bigand_1996$chord_type) %>% I +bigand_1996$pitches <- mapply( + function(pc_set, + bass_interval_size, tenor_interval_size, + alto_interval_size, soprano_interval_size) { + deduce_chord_pitches( + pc_set = pc_set, + bass_interval_size = bass_interval_size, + tenor_interval_size = tenor_interval_size, + alto_interval_size = alto_interval_size, + soprano_interval_size = soprano_interval_size, + previous_chord_pitches = bigand_1996_reference_chord + ) + }, bigand_1996$pc_set, + bigand_1996$bass_interval_size, bigand_1996$tenor_interval_size, + bigand_1996$alto_interval_size, bigand_1996$soprano_interval_size, + SIMPLIFY = FALSE +) %>% I + +saveRDS(bigand_1996, "inst/bigand1996/compiled/bigand_1996.rds") +saveRDS(bigand_1996_reference_chord, "inst/bigand1996/compiled/bigand_1996_reference_chord.rds") diff --git a/inst/bigand1996/compiled/bigand_1996.rds b/inst/bigand1996/compiled/bigand_1996.rds new file mode 100644 index 0000000..de2a470 Binary files /dev/null and b/inst/bigand1996/compiled/bigand_1996.rds differ diff --git a/inst/bigand1996/compiled/bigand_1996_reference_chord.rds b/inst/bigand1996/compiled/bigand_1996_reference_chord.rds new file mode 100644 index 0000000..7d13501 Binary files /dev/null and b/inst/bigand1996/compiled/bigand_1996_reference_chord.rds differ diff --git a/inst/bigand1996/raw/bigand-1996-data.csv b/inst/bigand1996/raw/bigand-1996-data.csv new file mode 100644 index 0000000..d513461 --- /dev/null +++ b/inst/bigand1996/raw/bigand-1996-data.csv @@ -0,0 +1,51 @@ +"label","chord_type","bass_interval_size","tenor_interval_size","alto_interval_size","soprano_interval_size","roughness","pitch_commonality","lerdahl_distance","musician_tension" +"C","major",0,0,0,0,0.109,1,0,1.25 +"C#","major",1,6,1,4,0.072,-0.1,16,6.1 +"D","major",2,1,2,3,0.108,-0.02,10,6.1 +"D#","major",3,0,1,2,0.103,0.12,14,6.6 +"E","major",4,1,0,1,0.098,0.13,10,4.1 +"F","major",5,5,1,3,0.065,0.32,5,3.8 +"F#","major",6,6,2,2,0.061,-0.12,18,5.9 +"G","major",7,7,3,1,0.057,0.32,5,2.6 +"Ab","major",8,5,1,4,0.119,0.18,14,6 +"A","major",9,6,0,3,0.112,0.12,11,6.2 +"Bb","major",10,7,1,2,0.106,0.01,10,5.8 +"B","major",11,8,2,1,0.099,0.12,13,6.6 +"c","minor",0,0,1,0,0.145,0.83,2,3.5 +"c#","minor",1,6,0,4,0.093,0,17,7.6 +"d","minor",2,2,2,3,0.132,0.06,11,5.5 +"d#","minor",3,1,1,2,0.124,-0.09,16,7.1 +"e","minor",4,0,0,1,0.117,0.38,11,4.6 +"f","minor",5,5,1,4,0.116,0.24,7,6 +"f#","minor",6,6,2,3,0.111,-0.1,19,8 +"g","minor",7,7,3,2,0.105,0.39,6,6.9 +"ab","minor",8,4,1,4,0.123,-0.12,16,7 +"a","minor",9,5,0,3,0.115,0.46,12,5 +"bb","minor",10,6,1,2,0.108,-0.09,12,6.8 +"b","minor",11,7,2,1,0.102,-0.06,14,7.1 +"bdim","diminished",11,7,1,1,0.163,-0.02,8,NA +"c'","revoiced",0,5,0,5,0.077,0.91,0,NA +"C7","seventh",0,0,0,2,0.124,0.72,7,4.8 +"C#7","seventh",1,3,1,4,0.168,-0.1,20,6.5 +"D7","seventh",2,1,4,3,0.2,0.29,8,5.6 +"D#7","seventh",3,0,3,2,0.191,0.09,18,6 +"E7","seventh",4,1,2,1,0.182,0.02,7,5.6 +"F7","seventh",5,8,1,3,0.186,0.07,12,5.5 +"F#7","seventh",6,6,0,2,0.157,0.02,18,6.7 +"G7","seventh",7,7,1,1,0.149,0.21,9,3.3 +"Ab7","seventh",8,5,1,6,0.204,0.15,23,6.9 +"A7","seventh",9,6,0,5,0.192,0.36,7,8 +"Bb7","seventh",10,7,1,4,0.182,-0.02,16,7.5 +"B7","seventh",11,8,2,3,0.171,-0.11,11,8.1 +"c7","minor_seventh",0,0,1,2,0.147,0.45,9,5.8 +"c#7","minor_seventh",1,3,0,4,0.161,0.05,21,7.8 +"d7","minor_seventh",2,2,4,3,0.189,0.33,9,5.5 +"eb7","minor_seventh",3,1,3,2,0.18,-0.13,20,8 +"e7","minor_seventh",4,0,2,1,0.171,0.29,8,4.8 +"f7","minor_seventh",5,8,1,4,0.194,0.04,14,6 +"f#7","minor_seventh",6,6,0,3,0.185,0.07,19,7.3 +"g7","minor_seventh",7,7,1,2,0.176,0.21,10,7.6 +"ab7","minor_seventh",8,3,1,6,0.186,-0.14,24,8.8 +"a7","minor_seventh",9,5,0,5,0.175,0.75,8,7.1 +"bb7","minor_seventh",10,6,1,4,0.165,-0.11,18,8 +"b7","minor_seventh",11,7,2,3,0.155,-0.05,12,8.3 diff --git a/inst/bigand1996/raw/bigand-1996-reference-chord.csv b/inst/bigand1996/raw/bigand-1996-reference-chord.csv new file mode 100644 index 0000000..5863520 --- /dev/null +++ b/inst/bigand1996/raw/bigand-1996-reference-chord.csv @@ -0,0 +1,2 @@ +bass,tenor,alto,soprano +48,55,64,72 diff --git a/inst/bowling-data/README.txt b/inst/bowling-data/README.txt new file mode 100644 index 0000000..fd0b0af --- /dev/null +++ b/inst/bowling-data/README.txt @@ -0,0 +1,2 @@ +These datasets were constructed by reformatting the supplementary materials +in Bowling et al.'s (2018) paper. diff --git a/inst/bowling-data/dyads.csv b/inst/bowling-data/dyads.csv new file mode 100644 index 0000000..8346f50 --- /dev/null +++ b/inst/bowling-data/dyads.csv @@ -0,0 +1,13 @@ +name,pc_1,pc_2,f_1,f_2,rating_rank,rating_mean,rating_sd,rating_se,bowling_harm_sim,bowling_min_freq_int +minor 2nd,0,1,253.19,270.07,11,1.317,0.542,0.099,0.125,16.88 +major 2nd,0,2,246.24,277.02,9,1.837,0.653,0.119,0.222,30.78 +minor 3rd,0,3,237.85,285.41,6,2.693,0.538,0.098,0.333,47.56 +major 3rd,0,4,232.56,290.7,3,3.173,0.61,0.111,0.4,58.14 +perfect 4th,0,5,224.25,299.01,4,2.95,0.598,0.109,0.5,74.76 +tritone,0,6,218.03,305.24,8,1.953,0.395,0.072,0.314,87.21 +perfect 5th,0,7,209.3,313.96,2,3.447,0.591,0.108,0.667,104.66 +minor 6th,0,8,201.25,322.01,7,2.477,0.577,0.105,0.3,120.76 +major 6th,0,9,196.22,327.04,5,2.713,0.686,0.125,0.467,130.82 +minor 7th,0,10,186.88,336.38,10,1.753,0.359,0.066,0.289,149.5 +major 7th,0,11,182,341.26,10,1.753,0.541,0.099,0.183,159.26 +octave,0,12,174.42,348.84,1,3.893,0.274,0.05,1,174.42 \ No newline at end of file diff --git a/inst/bowling-data/tetrads.csv b/inst/bowling-data/tetrads.csv new file mode 100644 index 0000000..e2a83a9 --- /dev/null +++ b/inst/bowling-data/tetrads.csv @@ -0,0 +1,221 @@ +name,pc_1,pc_2,pc_3,pc_4,f_1,f_2,f_3,f_4,rating_rank,rating_mean,rating_sd,rating_se,bowling_harm_sim,bowling_min_freq_int +,0,1,2,3,238.3,254.18,268.08,285.96,63,1.400,0.621,0.113,0.027,13.90 +,0,1,2,4,235.61,251.32,265.07,294.52,60,1.500,0.630,0.115,0.027,13.75 +,0,1,2,5,231.28,246.69,260.18,308.37,54,1.700,0.837,0.153,0.025,13.49 +,0,1,2,6,227.92,243.11,256.41,319.08,57,1.600,0.724,0.132,0.027,13.30 +suspended 2nd + m2,0,1,2,7,223.06,237.93,250.94,334.59,61,1.467,0.571,0.104,0.024,13.01 +,0,1,2,8,218.4,232.96,245.7,349.45,61,1.467,0.571,0.104,0.024,12.74 +,0,1,2,9,215.41,229.77,242.33,359.01,62,1.433,0.568,0.104,0.025,12.56 +,0,1,2,10,209.65,223.63,235.86,377.38,68,1.233,0.504,0.092,0.026,12.23 +,0,1,2,11,206.55,220.32,232.37,387.28,72,1.033,0.183,0.033,0.025,12.05 +,0,1,2,12,201.58,215.02,226.77,403.15,71,1.100,0.403,0.074,0.022,11.75 +,0,1,3,4,231.7,247.15,278.04,289.63,63,1.400,0.675,0.123,0.051,11.59 +,0,1,3,5,227.5,242.67,273.01,303.34,50,1.833,0.592,0.108,0.189,15.17 +,0,1,3,6,224.25,239.2,269.11,313.96,58,1.567,0.728,0.133,0.194,14.95 +minor triad (r) + m2,0,1,3,7,219.55,234.19,263.46,329.32,49,1.867,0.730,0.133,0.093,14.64 +,0,1,3,8,215.04,229.37,258.05,344.06,45,2.000,0.695,0.127,0.175,14.33 +,0,1,3,9,212.13,226.27,254.56,353.55,58,1.567,0.626,0.114,0.189,14.14 +,0,1,3,10,206.55,220.32,247.86,371.79,65,1.333,0.547,0.100,0.179,13.77 +,0,1,3,11,203.54,217.11,244.24,381.63,64,1.367,0.615,0.112,0.024,13.57 +,0,1,3,12,198.71,211.95,238.45,397.41,57,1.600,0.855,0.156,0.164,13.24 +,0,1,4,5,225.06,240.06,281.32,300.08,51,1.800,0.761,0.139,0.048,15.00 +,0,1,4,6,221.88,236.67,277.35,310.63,59,1.533,0.730,0.133,0.050,14.79 +major triad (r) + m2,0,1,4,7,217.27,231.76,271.59,325.91,53,1.733,0.785,0.143,0.046,14.49 +,0,1,4,8,212.85,227.04,266.06,340.56,53,1.733,0.828,0.151,0.045,14.19 +,0,1,4,9,210,224,262.51,350.01,54,1.700,0.596,0.109,0.048,14.00 +,0,1,4,10,204.53,218.17,255.66,368.16,56,1.633,0.615,0.112,0.048,13.64 +,0,1,4,11,201.58,215.02,251.97,377.96,59,1.533,0.629,0.115,0.023,13.44 +,0,1,4,12,196.84,209.96,246.05,393.68,50,1.833,0.747,0.136,0.041,13.12 +,0,1,5,6,218.03,232.56,290.7,305.24,64,1.367,0.615,0.112,0.186,14.53 +suspended 4th + m2,0,1,5,7,213.58,227.81,284.77,320.36,45,2.000,0.695,0.127,0.086,14.23 +,0,1,5,8,209.3,223.26,279.07,334.89,41,2.133,0.507,0.093,0.167,13.96 +,0,1,5,9,206.55,220.32,275.4,344.25,54,1.700,0.466,0.085,0.170,13.77 +,0,1,5,10,201.25,214.67,268.34,362.26,44,2.033,0.556,0.102,0.178,13.42 +,0,1,5,11,198.39,211.62,264.52,371.99,52,1.767,0.728,0.133,0.023,13.23 +power chord + m2,0,1,5,12,193.8,206.72,258.4,387.6,36,2.300,0.877,0.160,0.150,12.92 +,0,1,6,7,210.71,224.76,294.99,316.06,63,1.400,0.563,0.103,0.091,14.05 +,0,1,6,8,206.55,220.32,289.17,330.48,54,1.700,0.750,0.137,0.175,13.77 +,0,1,6,9,203.87,217.46,285.41,339.78,62,1.433,0.504,0.092,0.186,13.59 +,0,1,6,10,198.71,211.95,278.19,357.67,49,1.867,0.571,0.104,0.185,13.24 +,0,1,6,11,195.92,208.98,274.28,367.34,53,1.733,0.691,0.126,0.024,13.06 +,0,1,6,12,191.44,204.2,268.01,382.87,54,1.700,0.750,0.137,0.161,12.76 +,0,1,7,8,202.55,216.06,303.83,324.08,60,1.500,0.509,0.093,0.082,13.51 +,0,1,7,9,199.97,213.3,299.96,333.29,65,1.333,0.479,0.088,0.086,13.33 +,0,1,7,10,195,208,292.51,351.01,48,1.900,0.759,0.139,0.088,13.00 +,0,1,7,11,192.32,205.14,288.47,360.59,56,1.633,0.490,0.089,0.022,12.82 +power chord + m2,0,1,7,12,188,200.53,282,376,52,1.767,0.774,0.141,0.074,12.53 +,0,1,8,9,196.22,209.3,313.96,327.04,63,1.400,0.563,0.103,0.167,13.08 +,0,1,8,10,191.44,204.2,306.3,344.59,46,1.967,0.718,0.131,0.168,12.76 +,0,1,8,11,188.85,201.44,302.15,354.09,57,1.600,0.563,0.103,0.022,12.59 +,0,1,8,12,184.68,196.99,295.49,369.36,34,2.367,0.964,0.176,0.142,12.31 +,0,1,9,10,189.13,201.74,315.22,340.43,68,1.233,0.430,0.079,0.178,12.61 +,0,1,9,11,186.6,199.04,311,349.88,69,1.200,0.484,0.088,0.023,12.44 +,0,1,9,12,182.53,194.7,304.22,365.07,54,1.700,0.702,0.128,0.150,12.17 +,0,1,10,11,182.27,194.42,328.08,341.75,65,1.333,0.479,0.088,0.023,12.15 +,0,1,10,12,178.38,190.28,321.09,356.77,51,1.800,0.714,0.130,0.153,11.90 +,0,1,11,12,176.13,187.87,330.25,352.26,63,1.400,0.621,0.113,0.020,11.74 +,0,2,3,4,228.75,257.34,274.5,285.93,55,1.667,0.711,0.130,0.074,11.43 +,0,2,3,5,224.66,252.74,269.59,299.54,45,2.000,0.788,0.144,0.025,16.85 +,0,2,3,6,221.49,249.17,265.78,310.08,54,1.700,0.596,0.109,0.073,16.61 +minor triad (r) + M2,0,2,3,7,216.9,244.01,260.27,325.34,25,2.667,0.994,0.182,0.067,16.26 +,0,2,3,8,212.49,239.05,254.99,339.99,42,2.100,0.885,0.162,0.069,15.94 +,0,2,3,9,209.65,235.86,251.58,349.42,51,1.800,0.610,0.111,0.024,15.72 +,0,2,3,10,204.2,229.72,245.04,367.56,53,1.733,0.640,0.117,0.067,15.32 +,0,2,3,11,201.25,226.41,241.5,377.35,54,1.700,0.651,0.119,0.069,15.09 +,0,2,3,12,196.53,221.1,235.84,393.06,54,1.700,0.596,0.109,0.061,14.74 +,0,2,4,5,222.27,250.05,277.84,296.36,34,2.367,0.718,0.131,0.117,18.52 +,0,2,4,6,219.17,246.56,273.96,306.83,37,2.267,0.640,0.117,0.072,27.39 +major triad (r) + M2,0,2,4,7,214.67,241.5,268.34,322.01,4,3.533,0.571,0.104,0.311,26.83 +,0,2,4,8,210.36,236.65,262.94,336.57,42,2.100,0.662,0.121,0.070,26.29 +,0,2,4,9,207.57,233.52,259.47,345.96,35,2.333,0.661,0.121,0.113,25.95 +,0,2,4,10,202.23,227.5,252.78,364.01,49,1.867,0.629,0.115,0.069,25.27 +,0,2,4,11,199.34,224.25,249.17,373.76,39,2.200,0.805,0.147,0.311,24.91 +,0,2,4,12,194.7,219.04,243.38,389.4,29,2.533,0.776,0.142,0.289,24.34 +,0,2,5,6,215.41,242.33,287.21,301.57,56,1.633,0.615,0.112,0.024,14.36 +mu chord,0,2,5,7,211.06,237.45,281.42,316.59,26,2.633,0.669,0.122,0.104,26.39 +,0,2,5,8,206.89,232.75,275.85,331.02,38,2.233,0.728,0.133,0.023,25.86 +,0,2,5,9,204.2,229.72,272.27,340.33,23,2.767,0.774,0.141,0.107,25.52 +,0,2,5,10,199.02,223.9,265.36,358.24,32,2.433,0.898,0.164,0.023,24.88 +,0,2,5,11,196.22,220.75,261.63,367.92,35,2.333,0.758,0.138,0.108,24.53 +power chord + M2,0,2,5,12,191.73,215.69,255.64,383.46,29,2.533,0.776,0.142,0.095,23.96 +suspended 2nd + tt,0,2,6,7,208.26,234.3,291.57,312.39,35,2.333,0.711,0.130,0.064,20.82 +,0,2,6,8,204.2,229.72,285.88,326.72,50,1.833,0.747,0.136,0.069,25.52 +,0,2,6,9,201.58,226.77,282.21,335.96,21,2.833,0.747,0.136,0.023,25.19 +,0,2,6,10,196.53,221.1,275.14,353.75,48,1.900,0.607,0.111,0.068,24.57 +,0,2,6,11,193.8,218.03,271.32,363.38,31,2.467,0.629,0.115,0.067,24.23 +,0,2,6,12,189.42,213.09,265.18,378.83,23,2.767,0.626,0.114,0.059,23.67 +suspended 2nd + m6,0,2,7,8,200.29,225.33,300.44,320.47,50,1.833,0.747,0.136,0.063,20.03 +suspended 2nd + M6,0,2,7,9,197.77,222.49,296.65,329.61,33,2.400,0.894,0.163,0.100,24.72 +suspended 2nd + m7,0,2,7,10,192.91,217.02,289.36,347.23,34,2.367,0.850,0.155,0.061,24.11 +suspended 2nd + M7,0,2,7,11,190.28,214.06,285.41,356.77,20,2.867,0.819,0.150,0.283,23.78 +suspended 2nd + Oct,0,2,7,12,186.05,209.3,279.07,372.1,13,3.100,0.662,0.121,0.250,23.25 +,0,2,8,9,194.1,218.36,310.56,323.5,63,1.400,0.563,0.103,0.022,12.94 +,0,2,8,10,189.42,213.09,303.06,340.95,40,2.167,0.791,0.145,0.067,23.67 +,0,2,8,11,186.88,210.24,299.01,350.4,40,2.167,0.648,0.118,0.065,23.36 +,0,2,8,12,182.8,205.65,292.48,365.6,32,2.433,0.774,0.141,0.057,22.85 +,0,2,9,10,187.16,210.55,311.93,336.88,59,1.533,0.571,0.104,0.022,23.39 +,0,2,9,11,184.68,207.77,307.8,346.28,43,2.067,0.691,0.126,0.104,23.09 +,0,2,9,12,180.69,203.28,301.16,361.39,27,2.600,0.724,0.132,0.091,22.59 +,0,2,10,11,180.43,202.99,324.78,338.31,62,1.433,0.626,0.114,0.063,13.53 +,0,2,10,12,176.63,198.71,317.93,353.26,42,2.100,0.712,0.130,0.056,22.08 +,0,2,11,12,174.42,196.22,327.04,348.84,41,2.133,0.681,0.124,0.261,21.80 +,0,3,4,5,218.78,262.54,273.48,291.71,61,1.467,0.571,0.104,0.046,10.94 +,0,3,4,6,215.78,258.93,269.72,302.09,54,1.700,0.750,0.137,0.137,10.79 +major triad (r) + m3,0,3,4,7,211.42,253.7,264.27,317.13,51,1.800,0.714,0.130,0.127,10.57 +,0,3,4,8,207.23,248.68,259.04,331.57,41,2.133,0.776,0.142,0.130,10.36 +,0,3,4,9,204.53,245.44,255.66,340.89,55,1.667,0.711,0.130,0.044,10.22 +,0,3,4,10,199.34,239.2,249.17,358.81,50,1.833,0.699,0.128,0.124,9.97 +,0,3,4,11,196.53,235.84,245.66,368.49,58,1.567,0.568,0.104,0.063,9.82 +,0,3,4,12,192.02,230.43,240.03,384.04,59,1.533,0.629,0.115,0.113,9.60 +,0,3,5,6,212.13,254.56,282.84,296.99,52,1.767,0.774,0.141,0.176,14.15 +minor triad (r) + P4,0,3,5,7,207.92,249.5,277.22,311.88,18,2.933,0.640,0.117,0.083,27.72 +,0,3,5,8,203.87,244.64,271.82,326.19,19,2.900,0.607,0.111,0.167,27.18 +,0,3,5,9,201.25,241.5,268.34,335.42,18,2.933,0.828,0.151,0.164,26.84 +,0,3,5,10,196.22,235.47,261.63,353.2,29,2.533,0.730,0.133,0.159,26.16 +,0,3,5,11,193.5,232.2,258,362.82,50,1.833,0.648,0.118,0.022,25.80 +power chord + m3,0,3,5,12,189.13,226.96,252.17,378.26,28,2.567,0.679,0.124,0.144,25.21 +minor triad (r) + tt,0,3,6,7,205.2,246.24,287.28,307.8,44,2.033,0.765,0.140,0.248,20.52 +,0,3,6,8,201.25,241.5,281.76,322.01,16,3.000,0.830,0.152,0.486,40.25 +dimished 7th chord,0,3,6,9,198.71,238.45,278.19,331.18,53,1.733,0.640,0.117,0.170,39.74 +half diminished 7th chord,0,3,6,10,193.8,232.56,271.32,348.84,27,2.600,0.932,0.170,0.467,38.76 +diminished (r) + M7,0,3,6,11,191.15,229.37,267.6,358.4,49,1.867,0.629,0.115,0.065,38.22 +diminished triad + oct,0,3,6,12,186.88,224.25,261.63,373.76,20,2.867,0.937,0.171,0.429,37.37 +minor triad (r) + m6,0,3,7,8,197.46,236.95,296.18,315.93,22,2.800,0.887,0.162,0.233,19.75 +minor triad (r) + M6,0,3,7,9,195,234,292.51,325.01,31,2.467,0.860,0.157,0.080,32.50 +minor 7th chord,0,3,7,10,190.28,228.33,285.41,342.5,14,3.067,0.868,0.159,0.222,38.05 +minor (r) + M7,0,3,7,11,187.72,225.26,281.57,351.97,47,1.933,0.740,0.135,0.060,37.54 +minor triad (r) + oct ,0,3,7,12,183.6,220.32,275.4,367.2,6,3.467,0.681,0.124,0.200,36.72 +,0,3,8,9,191.44,229.72,306.3,319.06,54,1.700,0.750,0.137,0.160,12.76 +,0,3,8,10,186.88,224.25,299.01,336.38,11,3.200,0.761,0.139,0.444,37.37 +,0,3,8,11,184.41,221.29,295.05,345.77,34,2.367,0.765,0.140,0.062,36.88 +,0,3,8,12,180.43,216.52,288.7,360.87,3,3.767,0.504,0.092,0.400,36.09 +,0,3,9,10,184.68,221.62,307.8,332.42,53,1.733,0.691,0.126,0.153,24.62 +,0,3,9,11,182.27,218.72,303.78,341.75,44,2.033,0.669,0.122,0.021,36.45 +,0,3,9,12,178.38,214.06,297.31,356.77,32,2.433,0.817,0.149,0.138,35.68 +,0,3,10,11,178.13,213.76,320.64,334,60,1.500,0.572,0.104,0.059,13.36 +,0,3,10,12,174.42,209.3,313.96,348.84,13,3.100,0.607,0.111,0.378,34.88 +,0,3,11,12,172.27,206.72,323,344.53,52,1.767,0.626,0.114,0.053,21.53 +,0,4,5,6,210,262.51,280.01,294.01,56,1.633,0.718,0.131,0.045,14.00 +major triad (r) + P4,0,4,5,7,205.87,257.34,274.5,308.81,22,2.800,0.805,0.147,0.197,17.16 +,0,4,5,8,201.9,252.38,269.2,323.04,49,1.867,0.629,0.115,0.043,16.82 +,0,4,5,9,199.34,249.17,265.78,332.23,16,3.000,0.695,0.127,0.200,16.61 +,0,4,5,10,194.4,243,259.2,349.92,45,2.000,0.947,0.173,0.042,16.20 +,0,4,5,11,191.73,239.66,255.64,359.49,42,2.100,0.923,0.168,0.099,15.98 +power chord + M3,0,4,5,12,187.44,234.3,249.92,374.87,30,2.500,0.777,0.142,0.175,15.62 +major triad (r) + tt,0,4,6,7,203.21,254.01,284.49,304.81,38,2.233,0.679,0.124,0.122,20.32 +,0,4,6,8,199.34,249.17,279.07,318.94,38,2.233,0.679,0.124,0.130,29.90 +,0,4,6,9,196.84,246.05,275.57,328.06,28,2.567,0.679,0.124,0.043,29.52 +dominant 7th flat 5,0,4,6,10,192.02,240.03,268.83,345.64,50,1.833,0.531,0.097,0.128,28.80 +,0,4,6,11,189.42,236.77,265.18,355.15,32,2.433,0.817,0.149,0.061,28.41 +augmented triad + oct,0,4,6,12,185.22,231.53,259.31,370.45,24,2.733,0.583,0.106,0.109,27.78 +major triad (r) + m6,0,4,7,8,195.61,244.51,293.42,312.98,48,1.900,0.481,0.088,0.118,19.56 +major triad (r) + M6,0,4,7,9,193.2,241.5,289.81,322.01,17,2.967,0.765,0.140,0.189,32.20 +dominant 7th chord,0,4,7,10,188.56,235.7,282.84,339.41,29,2.533,0.860,0.157,0.116,47.14 +major 7th chord,0,4,7,11,186.05,232.56,279.07,348.84,9,3.300,0.837,0.153,0.267,46.51 +major triad (r) + oct,0,4,7,12,182,227.5,273.01,364.01,1,3.833,0.592,0.108,0.467,45.50 +augmented triad + M6,0,4,8,9,189.7,237.13,303.52,316.17,55,1.667,0.661,0.121,0.042,12.65 +augmented triad + m7,0,4,8,10,185.22,231.53,296.36,333.4,54,1.700,0.702,0.128,0.124,37.04 +augmented triad + M7,0,4,8,11,182.8,228.5,292.48,342.75,46,1.967,0.490,0.089,0.059,45.70 +augmented triad + oct,0,4,8,12,178.89,223.62,286.23,357.78,34,2.367,0.718,0.131,0.105,44.73 +,0,4,9,10,183.06,228.83,305.11,329.52,58,1.567,0.626,0.114,0.041,24.41 +,0,4,9,11,180.69,225.87,301.16,338.8,25,2.667,0.758,0.138,0.094,37.64 +,0,4,9,12,176.88,221.1,294.79,353.75,10,3.233,0.728,0.133,0.167,44.22 +,0,4,10,11,176.63,220.78,317.93,331.18,54,1.700,0.702,0.128,0.058,13.25 +,0,4,10,12,172.98,216.22,311.36,345.96,31,2.467,0.681,0.124,0.102,34.60 +,0,4,11,12,170.86,213.58,320.36,341.72,8,3.333,0.479,0.088,0.233,21.36 +suspended 4th + tt,0,5,6,7,199.97,266.63,279.96,299.96,62,1.433,0.568,0.104,0.080,13.33 +,0,5,6,8,196.22,261.63,274.71,313.96,52,1.767,0.626,0.114,0.167,13.08 +,0,5,6,9,193.8,258.4,271.32,323,38,2.233,0.935,0.171,0.158,12.92 +,0,5,6,10,189.13,252.17,264.78,340.43,43,2.067,0.691,0.126,0.163,12.61 +,0,5,6,11,186.6,248.8,261.24,349.88,50,1.833,0.531,0.097,0.021,12.44 +,0,5,6,12,182.53,243.38,255.55,365.07,41,2.133,0.973,0.178,0.138,12.17 +suspended 4th + m6,0,5,7,8,192.61,256.81,288.92,308.18,43,2.067,0.740,0.135,0.078,19.26 +suspended 4th + M6,0,5,7,9,190.28,253.7,285.41,317.13,10,3.233,0.898,0.164,0.356,31.71 +suspended 4th + m7,0,5,7,10,185.77,247.7,278.66,334.39,24,2.733,0.828,0.151,0.076,30.96 +suspended 4th + M7,0,5,7,11,183.33,244.44,275,343.75,26,2.633,0.718,0.131,0.093,30.56 +suspended 4th + oct,0,5,7,12,179.4,239.2,269.11,358.81,5,3.500,0.572,0.104,0.306,29.91 +,0,5,8,9,186.88,249.17,299.01,311.46,53,1.733,0.785,0.143,0.153,12.45 +,0,5,8,10,182.53,243.38,292.05,328.56,28,2.567,0.626,0.114,0.159,36.51 +,0,5,8,11,180.18,240.23,288.28,337.83,47,1.933,0.583,0.106,0.021,48.05 +,0,5,8,12,176.38,235.17,282.21,352.76,7,3.400,0.621,0.113,0.133,47.04 +,0,5,9,10,180.43,240.58,300.72,324.78,38,2.233,0.858,0.157,0.150,24.06 +,0,5,9,11,178.13,237.51,296.89,334,33,2.400,0.770,0.141,0.094,37.11 +,0,5,9,12,174.42,232.56,290.7,348.84,2,3.800,0.610,0.111,0.600,58.14 +,0,5,10,11,174.18,232.24,313.52,326.58,61,1.467,0.507,0.093,0.020,13.06 +power chord + m7,0,5,10,12,170.63,227.5,307.13,341.26,12,3.133,0.629,0.115,0.130,34.13 +,0,5,11,12,168.57,224.76,316.06,337.13,39,2.200,0.664,0.121,0.082,21.07 +,0,6,7,8,190.28,266.39,285.41,304.44,44,2.033,0.669,0.122,0.233,19.02 +,0,6,7,9,188,263.2,282,313.33,43,2.067,0.640,0.117,0.077,18.80 +,0,6,7,10,183.6,257.04,275.4,330.48,52,1.767,0.728,0.133,0.229,18.36 +,0,6,7,11,181.22,253.7,271.82,339.78,38,2.233,0.817,0.149,0.058,18.12 + ,0,6,7,12,177.38,248.33,266.06,354.75,31,2.467,0.819,0.150,0.190,17.73 +,0,6,8,9,184.68,258.55,295.49,307.8,63,1.400,0.621,0.113,0.160,12.31 +,0,6,8,10,180.43,252.61,288.7,324.78,39,2.200,0.761,0.139,0.467,36.08 +,0,6,8,11,178.13,249.38,285.01,334,46,1.967,0.556,0.102,0.062,35.63 +,0,6,8,12,174.42,244.19,279.07,348.84,14,3.067,0.640,0.117,0.400,34.88 +,0,6,9,10,178.38,249.74,297.31,321.09,58,1.567,0.679,0.124,0.157,23.78 +,0,6,9,11,176.13,246.59,293.55,330.25,38,2.233,0.626,0.114,0.020,36.70 +,0,6,9,12,172.5,241.5,287.51,345.01,23,2.767,0.774,0.141,0.131,46.01 +,0,6,10,11,172.27,241.17,310.08,323,62,1.433,0.679,0.124,0.060,12.92 +,0,6,10,12,168.79,236.31,303.83,337.59,45,2.000,0.830,0.152,0.390,33.76 +,0,6,11,12,166.78,233.49,312.71,333.55,60,1.500,0.630,0.115,0.051,20.84 +,0,7,8,9,181.48,272.22,290.36,302.46,66,1.300,0.535,0.098,0.074,12.10 +,0,7,8,10,177.38,266.06,283.8,319.28,42,2.100,0.607,0.111,0.222,17.74 +,0,7,8,11,175.15,262.72,280.24,328.41,46,1.967,0.718,0.131,0.056,17.52 +power chord + m6,0,7,8,12,171.56,257.34,274.5,343.12,22,2.800,0.887,0.162,0.183,17.16 +,0,7,9,10,175.39,263.09,292.32,315.71,50,1.833,0.648,0.118,0.073,23.39 +,0,7,9,11,173.22,259.83,288.7,324.78,39,2.200,0.805,0.147,0.089,28.87 +power chord + M6,0,7,9,12,169.71,254.56,282.84,339.41,15,3.033,0.964,0.176,0.289,28.28 +,0,7,10,11,169.48,254.22,305.06,317.77,58,1.567,0.679,0.124,0.054,12.71 +power chord + m7,0,7,10,12,166.11,249.17,299.01,332.23,32,2.433,0.774,0.141,0.178,33.22 +power chord added M7,0,7,11,12,164.16,246.24,307.8,328.32,37,2.267,0.785,0.143,0.217,20.52 +,0,8,9,10,172.5,276.01,287.51,310.51,68,1.233,0.430,0.079,0.153,11.50 +,0,8,9,11,170.4,272.63,283.99,319.49,61,1.467,0.629,0.115,0.020,11.36 +,0,8,9,12,167,267.2,278.33,334,65,1.333,0.479,0.088,0.127,11.13 +,0,8,10,11,166.78,266.84,300.2,312.71,67,1.267,0.450,0.082,0.059,12.51 +,0,8,10,12,163.52,261.63,294.33,327.04,38,2.233,0.774,0.141,0.378,32.70 +,0,8,11,12,161.62,258.6,303.05,323.25,60,1.500,0.572,0.104,0.049,20.20 +,0,9,10,11,165.02,275.04,297.04,309.42,70,1.167,0.379,0.069,0.019,12.38 +,0,9,10,12,161.83,269.72,291.3,323.67,55,1.667,0.802,0.146,0.123,21.58 +,0,9,11,12,159.98,266.63,299.96,319.96,49,1.867,0.776,0.142,0.078,20.00 +,0,10,11,12,156.78,282.21,293.97,313.56,65,1.333,0.661,0.121,0.048,11.76 diff --git a/inst/bowling-data/triads.csv b/inst/bowling-data/triads.csv new file mode 100644 index 0000000..1bf1b02 --- /dev/null +++ b/inst/bowling-data/triads.csv @@ -0,0 +1,67 @@ +name,pc_1,pc_2,pc_3,f_1,f_2,f_3,rating_rank,rating_mean,rating_sd,rating_se,bowling_harm_sim,bowling_min_freq_int +,0,1,2,245.92,262.31,276.66,46,1.066666667,0.253708132,0.046320556,0.022,14.35 +,0,1,3,240.27,256.29,288.33,34,1.666666667,0.758098044,0.138409133,0.164,16.02 +,0,1,4,236.65,252.43,295.81,38,1.5,0.572351471,0.104496604,0.041,15.78 +,0,1,5,230.85,246.24,307.8,26,2.1,0.661763579,0.120820947,0.150,15.39 +Viennese trichord,0,1,6,226.41,241.5,316.97,32,1.8,0.610257153,0.111417203,0.161,15.09 +,0,1,7,220.06,234.73,330.09,40,1.4,0.498272879,0.090971765,0.074,14.67 +,0,1,8,214.06,228.33,342.5,39,1.466666667,0.507416263,0.092641111,0.142,14.27 +,0,1,9,210.24,224.25,350.4,42,1.266666667,0.449776445,0.082117568,0.150,14.01 +,0,1,10,202.99,216.52,365.38,45,1.166666667,0.379049022,0.069204567,0.153,13.53 +,0,1,11,199.13,212.4,373.36,44,1.2,0.484234198,0.088408664,0.020,13.27 +,0,1,12,193.01,205.87,386.01,39,1.466666667,0.571346464,0.104313115,0.125,12.86 +,0,2,3,236.06,265.56,283.27,36,1.6,0.723973709,0.13217891,0.061,17.71 +,0,2,4,232.56,261.63,290.7,14,2.6,0.813676204,0.148556271,0.289,29.07 +,0,2,5,226.96,255.33,302.61,12,2.7,0.794376789,0.145032695,0.095,28.37 +,0,2,6,222.66,250.5,311.73,19,2.366666667,0.6149479,0.112273612,0.059,27.84 +suspended 2nd,0,2,7,216.52,243.59,324.78,10,2.933333333,0.691491807,0.126248554,0.250,27.07 +,0,2,8,210.71,237.05,337.13,33,1.766666667,0.678910554,0.123951542,0.057,26.34 +,0,2,9,207,232.88,345.01,30,1.9,0.803011573,0.146609184,0.091,25.88 +,0,2,10,199.97,224.97,359.95,32,1.8,0.610257153,0.111417203,0.056,25.00 +,0,2,11,196.22,220.75,367.92,27,2.033333333,0.808716878,0.147650826,0.261,24.53 +,0,2,12,190.28,214.06,380.55,23,2.2,0.71438423,0.130428119,0.222,23.78 +,0,3,4,227.5,273.01,284.38,44,1.2,0.406838102,0.074278135,0.113,11.37 +,0,3,5,222.14,266.57,296.18,18,2.4,0.723973709,0.13217891,0.144,29.61 +dimished triad (r) ,0,3,6,218.03,261.63,305.24,19,2.366666667,0.718395402,0.131160456,0.429,43.60 +minor triad (r) ,0,3,7,212.13,254.56,318.2,6,3.4,0.674664668,0.123176352,0.200,42.43 +major triad (1),0,3,8,206.55,247.86,330.48,4,3.633333333,0.6149479,0.112273612,0.400,41.31 +dimished triad (1),0,3,9,202.99,243.59,338.31,22,2.233333333,0.678910554,0.123951542,0.138,40.60 +,0,3,10,196.22,235.47,353.2,15,2.533333333,0.730296743,0.133333333,0.378,39.25 +,0,3,11,192.61,231.13,361.15,37,1.533333333,0.571346464,0.104313115,0.053,38.52 +,0,3,12,186.88,224.25,373.76,11,2.8,0.924755326,0.168836451,0.333,37.37 +,0,4,5,219.04,273.8,292.05,25,2.133333333,0.776079152,0.141692019,0.175,18.25 +,0,4,6,215.04,268.8,301.05,30,1.9,0.547722558,0.1,0.109,32.25 +major triad (r) ,0,4,7,209.3,261.63,313.96,2,3.8,0.406838102,0.074278135,0.467,52.33 +augmented triad ,0,4,8,203.87,254.83,326.19,29,1.933333333,0.63968383,0.116789754,0.105,50.96 +minor triad (1),0,4,9,200.4,250.5,334,9,3.033333333,0.808716878,0.147650826,0.167,50.10 +Italian 6th,0,4,10,193.8,242.25,348.84,17,2.433333333,0.773854363,0.14128583,0.102,48.45 +,0,4,11,190.28,237.85,356.77,14,2.6,0.621455466,0.113461726,0.233,47.57 +,0,4,12,184.68,230.85,369.36,5,3.533333333,0.571346464,0.104313115,0.400,46.17 +,0,5,6,210.24,280.32,294.33,41,1.366666667,0.668675135,0.122082818,0.138,14.01 +suspended 4th,0,5,7,204.75,273.01,307.13,8,3.133333333,0.819307249,0.149584354,0.306,34.12 +minor triad (2),0,5,8,199.55,266.06,319.28,11,2.8,0.664363839,0.121295687,0.133,53.22 +major triad (2),0,5,9,196.22,261.63,327.04,4,3.633333333,0.764890496,0.13964926,0.600,65.41 +,0,5,10,189.89,253.19,341.81,19,2.366666667,0.668675135,0.122082818,0.130,63.30 +,0,5,11,186.51,248.68,349.7,20,2.3,0.651258728,0.118903032,0.082,62.17 +power chord,0,5,12,181.13,241.5,362.26,3,3.666666667,0.479463301,0.087537622,0.500,60.37 +,0,6,7,201.25,281.76,301.88,34,1.666666667,0.606478435,0.110727306,0.190,20.12 +,0,6,8,196.22,274.71,313.96,13,2.633333333,0.668675135,0.122082818,0.400,39.25 +dimished triad (2),0,6,9,193.01,270.21,321.68,22,2.233333333,0.626062316,0.114302818,0.131,51.47 +,0,6,10,186.88,261.63,336.38,29,1.933333333,0.827681987,0.151113365,0.390,74.75 +,0,6,11,183.6,257.04,344.25,30,1.9,0.711966679,0.129986737,0.051,73.44 +,0,6,12,178.38,249.74,356.77,19,2.366666667,0.850287308,0.155240513,0.314,71.36 +,0,7,8,191.44,287.15,306.3,31,1.833333333,0.530668631,0.096886393,0.183,19.15 +,0,7,9,188.37,282.56,313.96,16,2.5,0.82000841,0.149712368,0.289,31.40 +,0,7,10,182.53,273.8,328.56,24,2.166666667,0.912870929,0.166666667,0.178,54.76 +,0,7,11,179.4,269.11,336.38,21,2.266666667,0.827681987,0.151113365,0.217,67.27 +power chord,0,7,12,174.42,261.63,348.84,1,3.833333333,0.379049022,0.069204567,0.667,87.21 +,0,8,9,183.96,294.33,306.6,44,1.2,0.406838102,0.074278135,0.127,12.27 +,0,8,10,178.38,285.41,321.09,28,1.966666667,0.718395402,0.131160456,0.378,35.68 +,0,8,11,175.39,280.63,328.86,35,1.633333333,0.556053417,0.101521,0.049,48.23 +,0,8,12,170.63,273.01,341.26,7,3.2,0.71438423,0.130428119,0.300,68.25 +,0,9,10,175.72,292.87,316.3,43,1.233333333,0.430183067,0.078540323,0.123,23.43 +,0,9,11,172.82,288.03,324.04,37,1.533333333,0.507416263,0.092641111,0.078,36.01 +,0,9,12,168.19,280.32,336.38,9,3.033333333,0.889917987,0.162476052,0.467,56.06 +,0,10,11,167.89,302.2,314.8,44,1.2,0.550861394,0.100573071,0.048,12.60 +,0,10,12,163.52,294.33,327.04,32,1.8,0.664363839,0.121295687,0.289,32.71 +,0,11,12,161,301.88,322.01,44,1.2,0.406838102,0.074278135,0.183,20.13 \ No newline at end of file diff --git a/inst/johnson-laird-2012-data/figure-2.csv b/inst/johnson-laird-2012-data/figure-2.csv new file mode 100644 index 0000000..48a80d4 --- /dev/null +++ b/inst/johnson-laird-2012-data/figure-2.csv @@ -0,0 +1,56 @@ +id,midi,mean_rating,roughness.jl,dual_process +1a,48 64 67,1.667,7.27,1 +1b,52 67 72,2.889,6.11,1 +1c,55 72 76,2.741,9.92,1 +2a,55 74 77,2.481,4,2 +2b,50 65 79,4.444,13.93,2 +2c,53 67 74,3.63,11.52,2 +3a,55 71 77,2.63,7.6,2 +3b,59 65 79,3.556,17.85,2 +3c,53 67 71,4,19.4,2 +4a,50 65 72,2.926,8.94,2 +4b,53 60 74,3.333,8.96,2 +4c,48 74 77,4.296,11.03,2 +5a,45 60 64,2.407,9.72,2 +5b,48 64 69,3.593,10.77,2 +5c,52 69 72,3.481,18.38,2 +6a,53 60 67,3.148,9.83,2 +6b,48 55 65,2.852,13.4,2 +6c,55 65 72,3.111,12.14,2 +7a,48 67 71,3.37,10.6,2 +7b,55 59 72,3.296,22.67,2 +7c,59 60 67,4.741,36.97,2 +8a,48 64 71,2.63,10.97,2 +8b,52 59 72,3.704,19.94,2 +8c,59 60 64,3.963,37.07,2 +9a,47 62 65,3.889,22.14,2 +9b,50 65 71,3.519,13.69,2 +9c,53 71 74,3.667,16.83,2 +10a,48 71 74,3.963,13.9,3 +10b,50 59 72,4.148,22.14,3 +10c,59 60 62,5.185,54.29,3 +11a,47 65 69,5.148,17.46,3 +11b,53 57 71,3.444,20.93,3 +11c,57 71 77,5.296,18.74,3 +12a,48 65 71,3.926,18.67,3 +12b,53 59 72,3.704,17.66,3 +12c,59 60 65,4.519,39.64,3 +13a,50 64 67,3.481,19.02,3 +13b,52 62 79,3.333,17.07,3 +13c,55 64 74,3.222,7.51,3 +14a,45 60 71,3.63,24.85,3 +14b,59 60 69,5.185,40.3,3 +14c,45 59 72,4,30.65,3 +15a,53 64 71,4.815,26.39,3 +15b,64 65 71,5.296,35.4,3 +15c,59 64 77,5.333,38.53,3 +16,48 64 68,5.259,16.07,4 +17a,48 63 71,5.593,17.27,4 +17b,51 59 72,5.815,25.87,4 +17c,59 60 63,4.148,41.16,4 +18a,48 68 71,5.63,21.64,4 +18b,56 59 72,4.259,23.31,4 +18c,59 60 68,5.296,39.95,4 +19a,48 62 73,5.593,30.42,5 +19b,49 60 74,5.852,29.1,5 +19c,50 61 72,6.667,33.88,5 \ No newline at end of file diff --git a/inst/johnson-laird-2012-data/figure-3.csv b/inst/johnson-laird-2012-data/figure-3.csv new file mode 100644 index 0000000..9f984ec --- /dev/null +++ b/inst/johnson-laird-2012-data/figure-3.csv @@ -0,0 +1,49 @@ +id,midi,mean_rating,roughness.jl,dual_process +1,43 53 59 62,2.54,22.1,1 +2,48 59 64 67,2.08,22.17,1 +3,48 62 64 67,2,24.08,1 +4a,48 62 64 71,2.03,27.62,1 +4b,52 60 62 71,3.59,32.81,1 +5,48 59 62 67,2.38,28.6,1 +6,43 53 57 59,3.38,40.39,1 +7a,48 57 64 67,2.46,14.41,2 +7b,45 55 60 64,2.46,19.78,2 +8a,48 57 62 67,2.41,17.51,2 +8b,45 55 60 62,3.16,31.94,2 +9a,48 62 64 69 ,2.81,24.91,2 +9b,50 60 64 69,3.22,18.87,2 +10a,48 59 64 69,2.92,25.2,2 +10b,45 59 60 64,3.11,39.16,2 +11a,48 59 62 69,2.95,32.41,2 +11b,50 59 60 69,4.54,38.63,2 +12,43 53 59 64,3.35,33.32,2 +13a,48 57 59 67,3.54,37.32,2 +13b,45 55 59 60,3.19,49.11,2 +14,43 53 59 60,3.49,41.92,2 +15a,50 59 65 69,3.43,15.6,3 +15b,47 57 62 65,3.14,30.63,3 +16a,53 59 69 72,4.83,20.7,3 +16b,47 57 60 65,4.76,38.15,3 +17,53 57 64 71,4.78,27.09,3 +18,53 59 64 72,4.84,28.22,3 +19,50 60 65 71,4.49,28.49,3 +20,50 53 64 71,4.81,36.45,3 +21,45 56 60 64,5.08,25.23,4 +22,48 59 64 68,4.54,26.69,4 +23a,50 60 64 68,4.73,28.34,4 +23b,52 62 68 72,4.73,23.29,4 +24,44 53 59 62,3.65,29.08,4 +25,53 57 68 72,4.78,20.56,5 +26a,53 64 68 74,5.22,25.35,5 +26b,52 62 65 68,4.57,29.23,5 +27,50 57 65 68,4.86,26.78,5 +28,47 56 65 69,5.46,29.92,5 +29,53 57 64 68,4.92,29.98,5 +30,45 60 68 71,4.73,30.2,5 +31,50 57 60 68,4.81,31.83,5 +32,53 56 64 71,5.38,31.88,5 +33,53 57 63 68,4.84,28.49,6 +34,53 57 63 71,4.97,28.73,6 +35,51 57 62 68,5.57,36.93,6 +36,53 63 64 74,5.95,57.43,6 +37,43 53 63 64,6.43,61.83,6 \ No newline at end of file diff --git a/inst/stolz15/data-formatted.csv b/inst/stolz15/data-formatted.csv new file mode 100644 index 0000000..274d0b5 --- /dev/null +++ b/inst/stolz15/data-formatted.csv @@ -0,0 +1,2049 @@ +chord,periodicity.stolz_smooth_t2_log,omega.stolz_smooth_t2,gradus.euler_smooth_t2,similarity.gill_purves_smooth_t2 +{0},0.000,0.000,1.000,1.000000 +"{0,7}",1.000,2.000,4.000,0.666667 +"{0,9}",1.585,2.000,7.000,0.466667 +"{0,5}",1.585,3.000,5.000,0.500000 +"{0,4}",2.000,3.000,7.000,0.400000 +"{0,3}",2.322,3.000,8.000,0.333333 +"{0,8}",2.322,4.000,8.000,0.300000 +"{0,6}",2.565,2.500,11.500,0.314286 +"{0,10}",2.746,4.500,9.000,0.288889 +"{0,11}",3.000,5.000,10.000,0.183333 +"{0,2}",3.085,4.500,9.000,0.222222 +"{0,1}",3.907,6.000,11.000,0.125000 +"{0,5,9}",1.585,4.000,9.000,0.455556 +"{0,4,7}",2.000,4.000,9.000,0.466667 +"{0,3,8}",2.322,5.000,10.000,0.377778 +"{0,4,11}",3.000,5.000,10.000,0.416667 +"{0,7,11}",3.000,5.000,10.000,0.416667 +"{0,7,9}",3.057,5.000,10.333,0.451852 +"{0,3,10}",3.133,5.333,10.667,0.429630 +"{0,5,7}",3.164,5.333,9.667,0.462963 +"{0,3,7}",3.322,4.000,9.000,0.466667 +"{0,7,8}",3.322,6.000,11.000,0.363889 +"{0,8,10}",3.379,6.333,12.333,0.270370 +"{0,5,10}",3.416,6.000,10.333,0.429630 +"{0,9,10}",3.416,6.333,13.667,0.293519 +"{0,4,9}",3.585,4.000,9.000,0.455556 +"{0,2,7}",3.585,5.667,10.000,0.462963 +"{0,2,11}",3.585,6.000,13.333,0.290741 +"{0,4,5}",3.585,6.000,11.000,0.341667 +"{0,6,9}",3.628,4.667,15.333,0.371429 +"{0,2,9}",3.642,5.000,11.333,0.451852 +"{0,2,5}",3.642,6.000,11.333,0.351852 +"{0,3,9}",3.712,4.667,15.333,0.371429 +"{0,2,4}",3.723,6.000,12.000,0.281481 +"{0,3,6}",3.786,5.000,15.667,0.326984 +"{0,7,10}",3.800,5.333,11.667,0.429630 +"{0,6,10}",3.874,6.000,15.667,0.334392 +"{0,2,10}",3.887,6.667,12.667,0.270370 +"{0,5,8}",3.907,5.000,10.000,0.377778 +"{0,1,5}",3.907,6.000,11.000,0.341667 +"{0,1,8}",3.907,6.000,11.000,0.363889 +"{0,6,8}",3.924,6.333,16.000,0.278836 +"{0,4,10}",3.938,6.000,15.667,0.334392 +"{0,4,6}",4.043,6.000,15.667,0.312169 +"{0,3,5}",4.045,5.667,12.000,0.351852 +"{0,2,6}",4.100,6.000,15.667,0.312169 +"{0,1,3}",4.153,6.667,14.000,0.226852 +"{0,5,11}",4.164,6.667,15.333,0.332540 +"{0,6,7}",4.238,6.667,15.333,0.368651 +"{0,4,8}",4.322,6.667,13.667,0.366667 +"{0,2,8}",4.379,6.667,16.333,0.278836 +"{0,9,11}",4.390,6.667,13.000,0.290741 +"{0,1,6}",4.402,7.000,15.667,0.313095 +"{0,8,11}",4.655,7.667,15.667,0.272222 +"{0,6,11}",4.905,7.000,16.667,0.332540 +"{0,1,9}",4.907,7.667,15.667,0.297222 +"{0,1,10}",4.964,7.333,13.667,0.293519 +"{0,3,4}",4.989,7.667,15.667,0.286111 +"{0,5,6}",5.069,7.333,17.000,0.313095 +"{0,2,3}",5.133,7.667,14.000,0.226852 +"{0,1,7}",5.153,7.000,16.667,0.368651 +"{0,8,9}",5.240,7.667,15.667,0.297222 +"{0,3,11}",5.322,7.667,15.667,0.272222 +"{0,1,4}",5.574,7.667,15.667,0.286111 +"{0,10,11}",6.466,10.000,18.333,0.199074 +"{0,1,11}",6.712,10.000,18.333,0.199074 +"{0,1,2}",6.964,10.333,18.667,0.157407 +"{0,4,7,11}",3.000,5.000,10.000,0.441667 +"{0,3,7,8}",3.322,6.000,11.000,0.387500 +"{0,5,9,10}",3.354,7.000,14.000,0.380093 +"{0,2,7,11}",3.439,6.500,13.500,0.406481 +"{0,3,8,10}",3.511,6.750,13.000,0.385185 +"{0,5,7,9}",3.519,6.500,12.750,0.412963 +"{0,2,5,9}",3.524,6.000,12.250,0.431481 +"{0,4,5,9}",3.585,6.000,11.000,0.398611 +"{0,3,7,10}",3.680,6.000,12.250,0.448148 +"{0,4,7,9}",3.689,5.750,12.000,0.431481 +"{0,4,5,7}",3.769,7.000,14.000,0.374537 +"{0,1,5,8}",3.907,6.000,11.000,0.387500 +"{0,2,4,11}",3.939,6.500,13.500,0.360185 +"{0,2,4,7}",3.939,6.750,13.000,0.390741 +"{0,3,5,8}",4.011,6.750,13.000,0.364815 +"{0,1,3,8}",4.091,7.250,14.250,0.357870 +"{0,2,9,10}",4.104,7.250,14.250,0.344907 +"{0,2,5,10}",4.104,7.500,13.750,0.357407 +"{0,3,5,9}",4.261,6.750,17.500,0.372751 +"{0,7,8,10}",4.261,7.500,14.500,0.322685 +"{0,5,9,11}",4.269,7.750,17.000,0.347751 +"{0,3,6,9}",4.278,6.500,20.500,0.349206 +"{0,7,9,11}",4.292,7.000,13.500,0.360185 +"{0,6,8,10}",4.316,7.750,17.750,0.291270 +"{0,6,9,10}",4.344,8.000,19.500,0.321362 +"{0,3,9,10}",4.369,7.500,18.000,0.365807 +"{0,2,7,9}",4.377,6.750,13.000,0.457407 +"{0,4,7,10}",4.430,6.750,17.500,0.389418 +"{0,4,9,11}",4.439,6.500,12.750,0.406481 +"{0,4,8,11}",4.491,7.750,15.750,0.380556 +"{0,3,6,8}",4.500,7.250,18.000,0.333862 +"{0,2,6,9}",4.514,6.750,17.500,0.400529 +"{0,5,7,10}",4.538,7.250,13.500,0.418519 +"{0,4,7,8}",4.572,8.000,16.000,0.370833 +"{0,2,4,6}",4.575,7.500,17.500,0.296825 +"{0,1,3,5}",4.591,7.250,14.250,0.300463 +"{0,3,5,7}",4.614,6.500,13.000,0.390741 +"{0,4,8,10}",4.614,8.250,18.500,0.320899 +"{0,6,7,9}",4.617,7.500,18.000,0.354696 +"{0,3,5,10}",4.619,7.250,13.500,0.418519 +"{0,4,6,10}",4.655,8.250,20.750,0.323280 +"{0,1,5,9}",4.657,8.000,16.000,0.365278 +"{0,2,8,10}",4.657,8.250,18.250,0.274603 +"{0,1,6,10}",4.674,8.250,17.500,0.349140 +"{0,4,6,7}",4.679,8.000,17.250,0.343585 +"{0,2,8,11}",4.680,8.250,19.750,0.303307 +"{0,4,6,8}",4.693,8.250,18.500,0.309788 +"{0,2,6,10}",4.698,8.250,18.500,0.320899 +"{0,5,8,10}",4.699,7.000,13.500,0.357407 +"{0,1,8,10}",4.699,7.500,14.000,0.344907 +"{0,5,7,11}",4.703,7.500,16.750,0.381085 +"{0,2,5,7}",4.708,7.250,13.500,0.407407 +"{0,2,5,11}",4.708,7.750,18.250,0.336640 +"{0,2,6,8}",4.736,8.500,21.000,0.295503 +"{0,2,4,10}",4.746,8.000,18.000,0.291270 +"{0,5,7,8}",4.761,7.500,13.750,0.357870 +"{0,4,5,11}",4.769,7.750,16.500,0.364881 +"{0,2,4,8}",4.784,8.250,18.500,0.309788 +"{0,3,4,6}",4.840,8.250,19.750,0.288029 +"{0,1,3,9}",4.841,8.250,19.750,0.293585 +"{0,5,6,9}",4.844,8.000,19.000,0.356548 +"{0,3,6,10}",4.859,6.750,16.750,0.389418 +"{0,1,3,6}",4.859,8.000,18.500,0.304696 +"{0,3,7,9}",4.864,6.500,16.500,0.400529 +"{0,2,4,9}",4.877,6.500,13.000,0.412963 +"{0,2,9,11}",4.877,7.250,15.000,0.371296 +"{0,2,4,5}",4.877,8.000,14.500,0.300463 +"{0,3,4,8}",4.902,8.250,16.250,0.343056 +"{0,5,8,9}",4.907,7.750,15.750,0.354167 +"{0,4,6,11}",4.929,7.750,17.000,0.381085 +"{0,6,7,11}",4.929,7.750,16.500,0.364881 +"{0,2,3,8}",5.011,8.750,18.000,0.299140 +"{0,2,7,10}",5.038,7.000,13.500,0.385185 +"{0,4,9,10}",5.038,7.500,17.500,0.349140 +"{0,7,9,10}",5.038,7.750,15.500,0.350463 +"{0,2,3,10}",5.038,8.000,14.500,0.322685 +"{0,4,5,10}",5.038,8.250,17.500,0.354696 +"{0,3,4,7}",5.072,7.500,15.500,0.376389 +"{0,7,8,11}",5.072,8.250,16.250,0.334722 +"{0,3,6,11}",5.090,8.250,19.250,0.327381 +"{0,1,5,7}",5.091,7.750,17.000,0.371362 +"{0,1,7,8}",5.091,8.000,16.750,0.366270 +"{0,3,8,9}",5.091,8.250,19.250,0.339881 +"{0,1,5,10}",5.096,7.250,13.500,0.380093 +"{0,1,6,8}",5.109,8.000,17.250,0.354696 +"{0,4,6,9}",5.117,6.750,16.750,0.372751 +"{0,2,6,11}",5.117,7.250,17.250,0.347751 +"{0,2,6,7}",5.117,8.000,17.250,0.371362 +"{0,3,8,11}",5.152,8.000,16.000,0.325000 +"{0,3,6,7}",5.170,7.750,18.000,0.362103 +"{0,1,5,6}",5.174,8.250,17.000,0.327381 +"{0,5,6,10}",5.174,8.250,17.500,0.354696 +"{0,2,3,7}",5.180,7.500,13.750,0.374537 +"{0,1,4,7}",5.261,8.000,19.000,0.362103 +"{0,5,8,11}",5.261,8.000,18.250,0.327381 +"{0,2,7,8}",5.261,8.500,17.750,0.354696 +"{0,1,3,7}",5.275,7.500,17.500,0.343585 +"{0,1,3,10}",5.280,7.750,15.500,0.350463 +"{0,1,4,9}",5.326,7.750,15.750,0.354167 +"{0,6,8,11}",5.340,9.250,20.000,0.308862 +"{0,5,6,8}",5.359,8.000,18.000,0.299140 +"{0,1,6,9}",5.424,8.000,18.250,0.339881 +"{0,3,4,9}",5.430,7.750,18.000,0.356548 +"{0,2,5,8}",5.449,7.500,17.500,0.333862 +"{0,1,8,9}",5.487,8.500,16.500,0.330556 +"{0,3,4,11}",5.491,8.250,16.250,0.334722 +"{0,1,7,9}",5.511,9.000,19.750,0.349140 +"{0,3,4,10}",5.511,9.000,19.750,0.354696 +"{0,6,8,9}",5.528,8.750,19.500,0.293585 +"{0,3,9,11}",5.534,8.500,19.250,0.303307 +"{0,3,7,11}",5.572,7.750,15.750,0.380556 +"{0,2,5,6}",5.594,9.250,20.000,0.315807 +"{0,4,10,11}",5.600,9.250,19.250,0.329696 +"{0,6,9,11}",5.617,8.000,18.500,0.336640 +"{0,2,3,5}",5.619,8.250,16.000,0.289352 +"{0,1,4,5}",5.657,8.500,16.500,0.313889 +"{0,6,7,8}",5.670,9.750,19.750,0.292196 +"{0,8,9,11}",5.680,9.500,18.750,0.271759 +"{0,8,9,10}",5.699,10.000,19.500,0.254630 +"{0,2,3,6}",5.778,8.750,19.500,0.288029 +"{0,6,7,10}",5.778,8.750,19.500,0.354696 +"{0,4,8,9}",5.826,8.000,16.000,0.365278 +"{0,1,9,10}",5.846,9.750,19.000,0.295370 +"{0,3,5,6}",5.859,8.500,19.000,0.304696 +"{0,3,5,11}",5.864,9.000,19.750,0.308862 +"{0,2,3,9}",5.869,8.250,18.750,0.354696 +"{0,2,8,9}",5.869,9.000,19.750,0.349140 +"{0,4,5,8}",5.907,8.250,16.250,0.343056 +"{0,2,3,11}",5.930,9.750,19.000,0.271759 +"{0,1,4,10}",5.949,8.750,19.500,0.321362 +"{0,1,4,8}",5.987,8.000,16.000,0.370833 +"{0,1,5,11}",6.011,9.500,19.500,0.301918 +"{0,3,10,11}",6.011,9.750,18.250,0.316204 +"{0,7,8,9}",6.011,9.750,18.250,0.317593 +"{0,8,10,11}",6.011,10.250,18.750,0.242130 +"{0,1,4,6}",6.028,9.000,19.750,0.315807 +"{0,1,7,10}",6.030,8.250,18.750,0.365807 +"{0,2,10,11}",6.038,10.000,19.500,0.264352 +"{0,1,3,4}",6.091,9.750,19.000,0.256481 +"{0,1,2,6}",6.174,9.750,19.750,0.281085 +"{0,1,4,11}",6.180,9.500,18.000,0.332870 +"{0,7,10,11}",6.180,9.500,18.750,0.332870 +"{0,1,9,11}",6.180,10.000,18.500,0.264352 +"{0,1,3,11}",6.195,10.000,19.500,0.242130 +"{0,6,10,11}",6.198,9.750,20.500,0.301918 +"{0,5,6,11}",6.198,10.000,22.500,0.322817 +"{0,1,6,7}",6.359,10.000,22.500,0.340873 +"{0,1,7,11}",6.364,9.500,20.250,0.329696 +"{0,1,2,4}",6.369,10.000,19.500,0.237963 +"{0,2,3,4}",6.430,10.250,18.750,0.237963 +"{0,4,5,6}",6.448,10.000,20.750,0.281085 +"{0,3,4,5}",6.511,10.000,19.250,0.284259 +"{0,5,6,7}",6.528,10.250,20.750,0.325529 +"{0,1,8,11}",6.591,9.750,19.000,0.316204 +"{0,1,2,5}",6.596,10.000,18.500,0.284259 +"{0,1,2,10}",6.596,10.500,19.000,0.254630 +"{0,5,10,11}",6.619,10.500,21.000,0.318585 +"{0,1,2,9}",6.765,10.000,19.250,0.317593 +"{0,1,6,11}",6.778,10.500,21.000,0.318585 +"{0,1,2,8}",6.780,10.000,20.750,0.292196 +"{0,9,10,11}",6.788,11.000,20.250,0.235185 +"{0,1,2,7}",6.949,10.500,21.000,0.325529 +"{0,1,10,11}",7.199,11.250,20.500,0.246296 +"{0,1,2,11}",7.369,11.250,20.500,0.235185 +"{0,1,2,3}",7.530,11.500,20.750,0.192130 +"{0,2,4,7,11}",3.751,6.800,13.600,0.406111 +"{0,2,5,9,10}",3.917,7.600,14.400,0.380278 +"{0,3,7,8,10}",4.073,7.600,14.400,0.383611 +"{0,4,5,7,9}",4.132,7.400,14.200,0.383611 +"{0,4,7,9,11}",4.351,6.800,13.200,0.406111 +"{0,1,3,5,8}",4.454,7.600,14.400,0.360278 +"{0,3,5,7,8}",4.673,7.800,14.200,0.360278 +"{0,2,7,9,11}",4.702,7.800,15.400,0.401667 +"{0,4,5,9,11}",4.732,8.200,16.800,0.377817 +"{0,2,5,7,9}",4.800,7.600,14.600,0.420000 +"{0,4,6,8,10}",4.853,9.400,21.800,0.308413 +"{0,1,5,8,10}",4.858,7.400,13.800,0.380278 +"{0,4,7,8,11}",4.858,8.200,16.200,0.380833 +"{0,5,7,9,10}",4.865,8.400,16.000,0.372500 +"{0,4,5,7,11}",4.880,8.000,17.200,0.381151 +"{0,5,7,9,11}",4.880,8.400,18.000,0.359762 +"{0,2,6,8,10}",4.887,9.600,22.000,0.298413 +"{0,2,6,9,10}",4.909,9.000,20.000,0.351706 +"{0,2,4,8,10}",4.926,9.600,22.000,0.298413 +"{0,2,4,5,9}",4.936,7.800,14.200,0.383611 +"{0,3,5,8,10}",4.941,8.000,15.000,0.386667 +"{0,4,6,7,11}",4.943,8.200,16.800,0.381151 +"{0,2,4,8,11}",4.944,8.800,19.800,0.350873 +"{0,2,4,6,10}",4.958,9.400,21.800,0.308413 +"{0,3,5,7,10}",4.959,7.800,14.800,0.413333 +"{0,5,6,9,10}",4.974,9.200,20.200,0.345317 +"{0,2,4,6,8}",4.989,9.400,21.800,0.301746 +"{0,3,5,9,10}",4.993,8.600,19.200,0.381706 +"{0,2,5,9,11}",5.000,8.200,18.200,0.377540 +"{0,1,3,8,10}",5.005,8.200,15.800,0.379167 +"{0,2,7,8,11}",5.009,9.200,20.200,0.351151 +"{0,3,6,8,10}",5.016,8.000,18.200,0.358095 +"{0,2,4,7,9}",5.019,7.600,14.600,0.420000 +"{0,2,5,7,11}",5.031,8.400,19.000,0.380873 +"{0,3,6,9,10}",5.038,8.600,22.000,0.357579 +"{0,1,5,7,8}",5.054,8.400,17.000,0.365317 +"{0,1,3,5,9}",5.054,9.000,20.000,0.328373 +"{0,4,5,9,10}",5.065,8.600,17.800,0.361984 +"{0,3,5,7,9}",5.073,7.800,18.000,0.374762 +"{0,4,7,8,10}",5.073,9.200,20.200,0.338373 +"{0,2,6,7,11}",5.094,8.200,17.400,0.377817 +"{0,2,3,7,10}",5.095,7.800,14.200,0.383611 +"{0,3,7,9,10}",5.159,8.400,18.400,0.381706 +"{0,1,5,8,9}",5.171,8.600,16.600,0.361667 +"{0,2,5,7,10}",5.182,8.200,15.200,0.386667 +"{0,1,6,8,10}",5.185,8.800,18.400,0.350595 +"{0,3,4,7,8}",5.186,8.600,16.600,0.358333 +"{0,3,4,6,8}",5.200,9.200,20.200,0.315040 +"{0,1,3,7,8}",5.202,8.400,17.600,0.365317 +"{0,4,5,7,10}",5.212,8.800,19.400,0.368373 +"{0,2,4,9,11}",5.219,7.600,15.200,0.401667 +"{0,1,5,6,10}",5.238,8.800,17.400,0.361984 +"{0,2,6,7,9}",5.245,8.600,19.200,0.391706 +"{0,3,5,8,9}",5.254,9.200,20.200,0.349484 +"{0,3,4,6,11}",5.272,9.200,20.200,0.337817 +"{0,4,6,8,11}",5.272,9.400,19.800,0.354206 +"{0,2,3,7,8}",5.273,9.000,17.600,0.348651 +"{0,2,4,5,7}",5.283,8.600,16.200,0.352500 +"{0,3,6,7,9}",5.287,8.600,22.000,0.350913 +"{0,2,4,6,11}",5.294,8.000,17.600,0.359762 +"{0,2,4,6,7}",5.294,8.800,18.400,0.340595 +"{0,1,3,6,10}",5.302,8.600,18.600,0.365040 +"{0,2,5,6,9}",5.309,9.000,20.000,0.376151 +"{0,1,3,8,9}",5.319,9.400,20.400,0.335317 +"{0,3,4,8,11}",5.322,8.600,16.600,0.354167 +"{0,5,8,9,11}",5.326,9.600,20.600,0.317817 +"{0,2,4,6,9}",5.328,7.800,18.000,0.374762 +"{0,1,3,6,8}",5.333,8.800,19.400,0.351706 +"{0,3,4,8,10}",5.337,9.400,19.800,0.355040 +"{0,2,7,9,10}",5.382,8.200,15.800,0.379167 +"{0,1,5,6,8}",5.385,8.600,17.800,0.348651 +"{0,5,6,8,10}",5.385,8.600,18.200,0.320595 +"{0,1,4,5,7}",5.390,9.200,20.200,0.341984 +"{0,1,5,7,9}",5.390,9.400,19.800,0.361706 +"{0,4,6,9,10}",5.392,9.400,22.200,0.336468 +"{0,3,6,8,11}",5.400,9.400,20.400,0.331984 +"{0,1,3,5,7}",5.402,8.200,17.800,0.340595 +"{0,2,4,7,8}",5.409,9.600,20.000,0.348373 +"{0,4,6,7,9}",5.411,8.400,18.400,0.358373 +"{0,2,6,8,11}",5.423,9.400,22.200,0.325635 +"{0,2,3,8,10}",5.424,9.200,18.800,0.327262 +"{0,1,6,9,10}",5.438,9.800,20.800,0.331984 +"{0,1,4,5,9}",5.443,8.800,16.800,0.355000 +"{0,2,3,5,10}",5.446,8.800,16.400,0.349167 +"{0,2,5,8,10}",5.458,8.600,18.800,0.331429 +"{0,2,4,9,10}",5.465,8.600,18.200,0.350595 +"{0,2,4,5,10}",5.465,9.400,19.000,0.320595 +"{0,3,4,7,10}",5.473,9.000,20.000,0.386151 +"{0,2,5,8,11}",5.476,9.000,22.400,0.330079 +"{0,2,4,5,11}",5.483,8.800,18.600,0.343373 +"{0,3,4,6,10}",5.487,9.400,22.200,0.339802 +"{0,6,7,9,11}",5.494,8.600,18.400,0.343373 +"{0,2,4,7,10}",5.495,8.200,18.400,0.358095 +"{0,3,5,6,9}",5.521,9.200,22.600,0.334246 +"{0,6,8,9,10}",5.521,10.400,21.800,0.279762 +"{0,1,4,7,9}",5.526,9.000,20.000,0.366151 +"{0,3,4,6,7}",5.536,9.600,20.600,0.325317 +"{0,1,3,7,9}",5.537,9.400,22.200,0.336468 +"{0,3,4,6,9}",5.540,8.800,22.200,0.334246 +"{0,5,7,8,10}",5.541,8.600,16.200,0.349167 +"{0,4,7,10,11}",5.544,9.000,19.400,0.371151 +"{0,2,5,6,10}",5.574,9.800,20.200,0.338373 +"{0,1,3,6,9}",5.585,9.000,22.400,0.324246 +"{0,3,7,8,11}",5.586,8.600,16.600,0.354167 +"{0,3,5,9,11}",5.609,9.800,22.600,0.325635 +"{0,4,8,10,11}",5.609,10.200,20.600,0.323373 +"{0,4,7,9,10}",5.612,8.400,19.000,0.365040 +"{0,3,6,9,11}",5.623,9.200,22.600,0.330079 +"{0,2,7,8,10}",5.624,9.000,18.600,0.327262 +"{0,2,6,9,11}",5.645,8.200,18.800,0.377540 +"{0,2,3,9,10}",5.646,9.000,18.800,0.350873 +"{0,3,4,7,11}",5.658,8.200,16.200,0.380833 +"{0,5,8,9,10}",5.658,9.600,19.000,0.326111 +"{0,4,8,9,11}",5.661,9.200,18.400,0.359722 +"{0,3,6,7,8}",5.665,9.600,20.000,0.331984 +"{0,3,6,8,9}",5.668,9.400,22.800,0.324246 +"{0,5,7,8,11}",5.673,9.200,19.600,0.337817 +"{0,2,3,8,11}",5.673,10.000,21.000,0.307817 +"{0,2,5,6,8}",5.721,9.800,22.600,0.306468 +"{0,5,6,8,9}",5.721,9.800,20.800,0.311984 +"{0,1,3,5,10}",5.722,8.200,15.800,0.372500 +"{0,4,5,8,11}",5.726,9.200,19.800,0.355595 +"{0,1,5,9,11}",5.726,10.200,20.600,0.320040 +"{0,3,6,7,11}",5.736,9.200,19.800,0.355595 +"{0,4,6,7,8}",5.736,10.400,20.800,0.310873 +"{0,3,8,10,11}",5.737,9.800,18.400,0.325278 +"{0,2,3,5,8}",5.741,9.200,19.200,0.318373 +"{0,3,6,7,10}",5.751,8.800,19.200,0.386151 +"{0,2,3,5,7}",5.759,8.400,16.000,0.352500 +"{0,2,8,10,11}",5.759,10.600,22.000,0.275595 +"{0,2,3,6,9}",5.774,9.200,22.600,0.350913 +"{0,6,7,9,10}",5.774,9.800,21.600,0.327540 +"{0,1,5,9,10}",5.775,9.400,18.600,0.357222 +"{0,1,3,5,6}",5.785,9.200,19.000,0.307540 +"{0,4,5,7,8}",5.790,9.600,18.800,0.340556 +"{0,1,3,4,9}",5.790,9.800,20.800,0.311984 +"{0,5,7,8,9}",5.790,9.800,18.400,0.336111 +"{0,2,3,5,9}",5.793,8.600,19.200,0.358373 +"{0,2,5,8,9}",5.793,9.000,19.400,0.366151 +"{0,2,8,9,10}",5.793,10.400,21.200,0.303095 +"{0,1,7,8,10}",5.805,9.000,18.800,0.350873 +"{0,3,8,9,10}",5.805,10.200,21.800,0.334206 +"{0,3,4,7,9}",5.809,8.600,19.000,0.376151 +"{0,2,3,7,11}",5.809,9.400,18.600,0.359722 +"{0,4,6,9,11}",5.811,8.400,18.400,0.380873 +"{0,2,8,9,11}",5.812,9.800,21.600,0.330040 +"{0,2,3,6,11}",5.823,9.600,20.600,0.317817 +"{0,4,6,7,10}",5.823,9.800,22.600,0.339802 +"{0,2,4,10,11}",5.831,10.000,20.800,0.318929 +"{0,1,5,6,9}",5.838,9.400,20.000,0.346429 +"{0,4,5,8,9}",5.843,8.800,16.800,0.355000 +"{0,1,4,7,8}",5.854,9.200,19.800,0.366429 +"{0,2,6,8,9}",5.857,10.000,22.800,0.336468 +"{0,1,4,9,11}",5.861,9.400,18.000,0.348611 +"{0,3,8,9,11}",5.873,9.800,20.800,0.307817 +"{0,1,3,9,11}",5.873,10.400,21.800,0.275595 +"{0,3,6,10,11}",5.887,9.600,20.000,0.344484 +"{0,3,7,9,11}",5.892,9.000,19.600,0.350873 +"{0,2,7,10,11}",5.895,9.400,18.800,0.348611 +"{0,1,4,8,9}",5.907,8.600,16.600,0.361667 +"{0,1,4,6,10}",5.921,10.000,22.800,0.336468 +"{0,1,3,9,10}",5.922,10.000,21.800,0.330873 +"{0,3,4,5,9}",5.926,9.600,20.000,0.338651 +"{0,3,5,6,8}",5.933,9.000,19.600,0.318373 +"{0,6,7,8,11}",5.936,10.400,20.800,0.316984 +"{0,3,5,8,11}",5.937,9.200,19.600,0.331984 +"{0,1,3,4,7}",5.937,9.600,20.600,0.325317 +"{0,3,7,8,9}",5.937,9.600,20.000,0.345317 +"{0,1,4,7,10}",5.941,9.200,22.600,0.357579 +"{0,3,4,9,11}",5.944,9.000,19.400,0.351151 +"{0,2,3,6,8}",5.951,10.200,23.000,0.306468 +"{0,6,7,8,10}",5.951,10.400,21.200,0.299762 +"{0,1,3,7,10}",5.953,8.600,19.200,0.381706 +"{0,3,5,7,11}",5.956,9.200,19.800,0.354206 +"{0,4,6,10,11}",5.958,10.200,22.400,0.341468 +"{0,2,3,7,9}",5.959,8.600,18.600,0.391706 +"{0,1,4,5,8}",5.971,8.600,16.600,0.358333 +"{0,1,6,8,9}",5.985,9.400,19.800,0.335317 +"{0,3,4,8,9}",5.990,9.400,20.000,0.346429 +"{0,1,4,9,10}",5.993,9.800,20.800,0.331984 +"{0,1,4,7,11}",6.009,9.200,19.600,0.371151 +"{0,3,4,10,11}",6.009,10.200,20.600,0.340317 +"{0,7,8,9,11}",6.009,10.200,19.600,0.304444 +"{0,2,3,4,6}",6.023,10.400,21.800,0.269762 +"{0,1,2,6,10}",6.038,10.600,21.000,0.314206 +"{0,1,5,8,11}",6.054,9.400,19.800,0.344484 +"{0,1,7,8,9}",6.054,10.400,20.800,0.331151 +"{0,1,4,6,9}",6.057,9.000,19.400,0.349484 +"{0,5,6,7,9}",6.057,10.200,21.800,0.337540 +"{0,3,4,5,7}",6.073,9.600,19.000,0.332778 +"{0,3,7,10,11}",6.073,9.800,19.000,0.369722 +"{0,1,5,7,11}",6.073,10.000,22.200,0.341468 +"{0,2,3,4,8}",6.073,10.600,21.000,0.294206 +"{0,7,8,10,11}",6.073,10.600,19.800,0.297778 +"{0,3,4,9,10}",6.076,10.000,22.800,0.353413 +"{0,4,8,9,10}",6.076,10.200,21.400,0.314206 +"{0,2,3,6,7}",6.087,9.400,19.800,0.341984 +"{0,2,6,7,8}",6.087,10.600,22.800,0.318968 +"{0,6,8,10,11}",6.087,10.600,21.400,0.288929 +"{0,3,5,6,10}",6.102,9.000,19.000,0.368373 +"{0,2,5,6,11}",6.109,10.000,22.800,0.335913 +"{0,2,6,10,11}",6.109,10.000,21.200,0.320040 +"{0,1,3,4,8}",6.119,9.600,18.800,0.340556 +"{0,1,5,7,10}",6.122,8.600,18.600,0.381706 +"{0,1,8,9,10}",6.122,10.400,19.800,0.308611 +"{0,4,7,8,9}",6.126,9.800,19.000,0.353889 +"{0,1,4,5,11}",6.126,10.200,20.600,0.333651 +"{0,1,3,5,11}",6.137,10.200,21.000,0.288929 +"{0,4,6,8,9}",6.140,9.400,20.000,0.328373 +"{0,2,5,7,8}",6.141,9.200,19.200,0.351706 +"{0,4,5,8,10}",6.141,9.600,20.200,0.338373 +"{0,2,3,5,11}",6.159,10.200,22.000,0.300040 +"{0,2,6,7,10}",6.174,9.400,20.000,0.355040 +"{0,1,2,6,9}",6.174,9.600,20.000,0.345317 +"{0,4,5,6,9}",6.192,9.800,20.200,0.338651 +"{0,5,6,9,11}",6.192,10.400,23.200,0.335913 +"{0,1,2,4,10}",6.193,10.600,22.000,0.279762 +"{0,1,3,8,11}",6.202,9.600,19.000,0.325278 +"{0,5,6,8,11}",6.204,10.400,23.200,0.312579 +"{0,1,4,8,10}",6.205,9.200,19.800,0.351706 +"{0,2,3,4,7}",6.209,9.800,18.400,0.332778 +"{0,1,7,9,11}",6.209,10.200,21.000,0.318929 +"{0,2,4,8,9}",6.212,9.400,20.000,0.361706 +"{0,1,3,6,7}",6.216,10.200,23.000,0.333413 +"{0,1,3,7,11}",6.220,10.000,21.200,0.323373 +"{0,2,3,6,10}",6.238,9.400,20.000,0.338373 +"{0,2,5,10,11}",6.246,10.600,22.200,0.323373 +"{0,1,2,4,6}",6.257,10.200,21.000,0.286429 +"{0,4,5,6,10}",6.257,10.600,22.800,0.318968 +"{0,1,4,5,10}",6.258,9.400,19.800,0.345317 +"{0,1,4,6,8}",6.268,9.400,20.000,0.348373 +"{0,1,3,4,6}",6.268,10.200,22.000,0.290873 +"{0,2,4,5,8}",6.276,9.600,20.200,0.315040 +"{0,2,3,9,11}",6.295,10.200,21.400,0.330040 +"{0,1,6,7,9}",6.321,10.200,23.000,0.336746 +"{0,1,2,4,9}",6.329,9.600,19.000,0.336111 +"{0,6,8,9,11}",6.340,10.400,21.600,0.300040 +"{0,2,3,4,11}",6.344,10.400,19.600,0.304444 +"{0,4,9,10,11}",6.348,10.400,21.000,0.329206 +"{0,1,3,6,11}",6.351,10.600,22.200,0.310040 +"{0,2,3,10,11}",6.359,10.600,20.000,0.301111 +"{0,3,9,10,11}",6.359,11.000,22.200,0.302540 +"{0,1,2,5,10}",6.375,10.000,18.600,0.326111 +"{0,2,9,10,11}",6.382,10.600,20.000,0.306667 +"{0,1,2,6,8}",6.385,10.400,22.600,0.318968 +"{0,1,4,8,11}",6.390,9.800,19.000,0.369722 +"{0,1,8,9,11}",6.390,10.600,19.800,0.301111 +"{0,2,4,5,6}",6.392,10.600,21.400,0.286429 +"{0,4,5,6,8}",6.404,10.400,21.600,0.294206 +"{0,6,7,10,11}",6.423,10.200,21.200,0.333651 +"{0,1,2,5,6}",6.438,10.600,21.000,0.304484 +"{0,3,4,5,8}",6.454,10.200,19.400,0.323889 +"{0,1,3,4,5}",6.454,10.600,19.800,0.278611 +"{0,1,7,9,10}",6.458,10.400,21.600,0.330873 +"{0,1,3,4,11}",6.473,10.400,19.800,0.297778 +"{0,1,2,4,7}",6.476,10.400,22.000,0.324206 +"{0,2,7,8,9}",6.476,10.600,21.200,0.360873 +"{0,3,5,6,11}",6.487,10.800,23.600,0.312579 +"{0,1,6,7,10}",6.502,10.600,23.400,0.353413 +"{0,6,9,10,11}",6.509,11.000,22.800,0.295873 +"{0,1,2,5,9}",6.510,10.000,19.200,0.353889 +"{0,1,2,9,10}",6.510,10.800,20.000,0.308611 +"{0,1,2,5,8}",6.522,9.800,20.200,0.331984 +"{0,1,2,8,10}",6.522,10.600,21.400,0.303095 +"{0,5,9,10,11}",6.529,11.200,22.400,0.312540 +"{0,1,7,8,11}",6.537,10.200,21.200,0.340317 +"{0,1,2,4,8}",6.541,10.200,21.400,0.310873 +"{0,5,8,10,11}",6.541,10.400,20.800,0.310040 +"{0,2,3,8,9}",6.541,10.800,23.600,0.336746 +"{0,7,8,9,10}",6.541,11.000,20.400,0.287500 +"{0,3,5,6,7}",6.551,10.200,20.600,0.324206 +"{0,2,3,4,10}",6.559,10.600,21.400,0.299762 +"{0,5,7,10,11}",6.559,10.600,21.800,0.353373 +"{0,1,2,4,5}",6.593,10.600,20.000,0.278611 +"{0,1,4,6,7}",6.604,10.600,23.400,0.333413 +"{0,6,7,8,9}",6.604,11.200,22.400,0.290040 +"{0,1,3,4,10}",6.605,10.400,21.600,0.327540 +"{0,3,4,5,11}",6.609,10.400,21.400,0.316984 +"{0,4,5,10,11}",6.612,11.000,23.200,0.341746 +"{0,5,6,7,11}",6.623,10.800,23.000,0.335079 +"{0,3,5,10,11}",6.624,10.800,21.400,0.343373 +"{0,2,3,5,6}",6.638,10.600,21.800,0.290873 +"{0,5,6,7,10}",6.638,10.600,21.200,0.347540 +"{0,1,6,9,11}",6.657,10.200,20.600,0.323373 +"{0,5,6,7,8}",6.668,11.000,21.600,0.293373 +"{0,4,5,6,11}",6.675,11.000,23.200,0.335079 +"{0,1,4,10,11}",6.676,11.000,22.200,0.319206 +"{0,1,3,10,11}",6.688,10.800,20.200,0.300000 +"{0,7,9,10,11}",6.695,10.600,20.000,0.303333 +"{0,1,4,5,6}",6.721,10.600,21.600,0.304484 +"{0,1,6,7,8}",6.733,11.000,23.200,0.335913 +"{0,1,4,6,11}",6.740,10.600,21.200,0.353373 +"{0,2,5,6,7}",6.774,10.800,22.000,0.340873 +"{0,5,6,10,11}",6.774,11.200,23.400,0.325079 +"{0,1,5,6,7}",6.785,11.000,23.200,0.329246 +"{0,2,3,4,9}",6.812,10.200,20.600,0.337540 +"{0,1,2,4,11}",6.812,10.600,20.000,0.303333 +"{0,1,7,10,11}",6.824,11.000,22.800,0.319206 +"{0,1,6,10,11}",6.838,11.400,22.600,0.319206 +"{0,1,2,5,7}",6.858,10.600,21.200,0.340873 +"{0,1,2,8,9}",6.858,10.600,21.600,0.331151 +"{0,1,5,10,11}",6.858,10.800,21.400,0.319206 +"{0,1,6,8,11}",6.868,10.800,22.000,0.343373 +"{0,8,9,10,11}",6.876,12.200,22.200,0.239167 +"{0,1,6,7,11}",6.887,11.000,23.200,0.341746 +"{0,1,5,6,11}",6.921,11.200,23.400,0.325079 +"{0,4,5,6,7}",6.940,11.400,22.600,0.303373 +"{0,3,4,5,10}",6.941,11.000,22.200,0.347540 +"{0,1,2,7,11}",6.959,11.200,22.400,0.329206 +"{0,1,2,6,11}",6.974,10.800,21.400,0.312540 +"{0,1,2,7,10}",6.975,10.400,20.800,0.334206 +"{0,1,2,7,9}",6.993,10.800,22.000,0.360873 +"{0,1,2,5,11}",6.993,11.200,22.400,0.295873 +"{0,1,9,10,11}",6.993,12.200,22.200,0.259167 +"{0,3,4,5,6}",7.004,11.400,23.200,0.270040 +"{0,1,8,10,11}",7.005,10.800,20.200,0.300000 +"{0,1,2,7,8}",7.005,11.200,23.400,0.335913 +"{0,1,2,6,7}",7.038,11.200,23.400,0.329246 +"{0,1,2,3,7}",7.088,11.000,21.600,0.303373 +"{0,1,2,3,6}",7.102,11.400,22.600,0.270040 +"{0,1,2,3,5}",7.122,11.000,20.400,0.260833 +"{0,1,2,9,11}",7.129,10.800,20.200,0.306667 +"{0,1,2,8,11}",7.141,11.200,23.000,0.302540 +"{0,1,2,3,9}",7.258,11.400,23.200,0.290040 +"{0,1,2,3,8}",7.270,11.600,22.800,0.293373 +"{0,2,3,4,5}",7.276,11.200,20.600,0.260833 +"{0,1,2,10,11}",7.310,12.400,22.400,0.259167 +"{0,1,2,3,11}",7.424,12.400,22.400,0.239167 +"{0,1,2,3,10}",7.439,11.200,20.600,0.287500 +"{0,1,2,3,4}",7.541,12.400,22.400,0.223333 +"{0,4,5,7,9,11}",4.997,8.500,17.500,0.374841 +"{0,2,4,7,9,11}",5.016,8.000,15.500,0.409259 +"{0,2,4,6,8,10}",5.072,10.500,24.500,0.302857 +"{0,2,4,7,8,11}",5.174,9.500,20.167,0.368915 +"{0,2,5,7,9,10}",5.180,8.667,16.167,0.383148 +"{0,2,4,6,7,11}",5.245,8.667,17.667,0.374841 +"{0,1,3,5,7,8}",5.319,8.833,17.833,0.355397 +"{0,3,5,7,8,10}",5.338,8.833,16.333,0.374259 +"{0,2,5,6,9,10}",5.340,9.833,20.500,0.358360 +"{0,2,5,7,9,11}",5.387,8.833,19.000,0.385767 +"{0,1,5,6,8,10}",5.403,9.000,18.000,0.357619 +"{0,2,4,5,9,10}",5.416,9.333,18.333,0.357619 +"{0,1,3,5,8,9}",5.417,9.833,20.500,0.347249 +"{0,1,3,5,8,10}",5.420,8.500,16.000,0.383148 +"{0,2,4,5,7,9}",5.431,8.667,16.167,0.385370 +"{0,2,4,5,7,11}",5.456,9.000,19.000,0.370397 +"{0,3,4,6,8,11}",5.500,9.833,20.500,0.342249 +"{0,3,4,7,8,10}",5.501,9.833,20.500,0.360582 +"{0,3,4,6,8,10}",5.513,9.833,22.000,0.336164 +"{0,2,4,6,8,11}",5.519,9.667,21.833,0.344497 +"{0,2,4,6,9,10}",5.522,9.833,22.000,0.345053 +"{0,2,6,7,9,11}",5.537,9.000,19.000,0.379286 +"{0,4,5,7,9,10}",5.538,9.333,19.333,0.359841 +"{0,3,5,7,9,10}",5.548,9.167,19.333,0.377434 +"{0,2,3,7,8,10}",5.574,9.333,18.333,0.353175 +"{0,1,4,5,7,9}",5.589,9.833,20.500,0.353915 +"{0,2,3,5,7,10}",5.592,8.833,16.333,0.374259 +"{0,2,4,5,9,11}",5.598,9.000,18.500,0.379286 +"{0,1,3,5,7,9}",5.599,9.833,22.000,0.340608 +"{0,2,3,5,9,10}",5.620,9.333,19.333,0.364286 +"{0,1,3,6,8,10}",5.623,9.167,19.333,0.370767 +"{0,3,4,7,8,11}",5.655,9.000,17.000,0.364444 +"{0,4,6,7,9,11}",5.676,8.833,18.333,0.370397 +"{0,2,5,6,8,10}",5.683,10.167,22.333,0.318386 +"{0,5,6,8,9,10}",5.683,10.500,21.500,0.310397 +"{0,2,4,7,8,10}",5.687,10.167,22.333,0.329497 +"{0,4,5,8,9,11}",5.702,10.000,20.667,0.351323 +"{0,4,7,8,10,11}",5.727,10.500,21.167,0.339471 +"{0,2,5,8,9,10}",5.743,10.000,20.500,0.339841 +"{0,2,4,7,10,11}",5.746,9.500,20.000,0.361508 +"{0,3,6,7,9,10}",5.752,10.000,23.000,0.354868 +"{0,2,3,7,9,10}",5.759,9.167,18.667,0.375397 +"{0,2,5,8,9,11}",5.759,10.000,23.000,0.345423 +"{0,1,3,7,8,10}",5.778,9.333,19.333,0.375397 +"{0,3,4,6,7,11}",5.780,9.833,20.500,0.353545 +"{0,3,5,6,9,10}",5.780,10.000,23.000,0.354868 +"{0,1,5,6,9,10}",5.780,10.167,20.833,0.349656 +"{0,4,6,7,8,11}",5.780,10.333,20.500,0.346138 +"{0,2,3,7,8,11}",5.781,10.167,20.833,0.344656 +"{0,3,4,8,10,11}",5.781,10.333,20.500,0.343915 +"{0,2,5,7,8,11}",5.784,10.000,23.000,0.347646 +"{0,2,6,8,9,10}",5.796,11.167,23.833,0.313386 +"{0,2,4,8,10,11}",5.800,11.000,23.667,0.317275 +"{0,2,4,6,7,9}",5.801,9.167,19.333,0.372989 +"{0,4,5,7,8,11}",5.825,9.833,20.500,0.353545 +"{0,5,7,8,9,11}",5.825,10.500,21.500,0.320952 +"{0,1,3,6,9,10}",5.834,10.167,23.167,0.345979 +"{0,3,6,8,10,11}",5.846,10.167,20.667,0.334841 +"{0,2,7,8,10,11}",5.853,10.667,21.667,0.318730 +"{0,1,3,5,6,8}",5.859,9.500,19.500,0.339841 +"{0,1,4,5,7,8}",5.863,10.000,20.667,0.349656 +"{0,1,5,7,8,9}",5.863,10.500,20.667,0.344471 +"{0,1,4,5,9,11}",5.869,10.333,20.500,0.348360 +"{0,3,5,8,9,11}",5.879,10.667,23.333,0.323201 +"{0,1,3,5,9,11}",5.879,11.000,23.667,0.306164 +"{0,3,4,6,7,8}",5.887,10.667,21.333,0.320026 +"{0,3,5,7,9,11}",5.894,10.000,22.333,0.344497 +"{0,3,6,7,8,10}",5.900,10.000,20.500,0.348730 +"{0,3,6,8,9,10}",5.903,10.500,23.500,0.329683 +"{0,1,6,8,9,10}",5.903,10.667,21.667,0.323730 +"{0,3,4,6,10,11}",5.906,10.500,22.667,0.344868 +"{0,2,6,7,8,11}",5.906,10.667,22.833,0.338201 +"{0,4,6,8,10,11}",5.906,10.833,23.000,0.326164 +"{0,1,4,5,8,9}",5.907,9.333,17.333,0.358333 +"{0,1,5,7,8,10}",5.920,9.167,18.667,0.364286 +"{0,3,5,7,8,9}",5.932,10.167,20.667,0.342063 +"{0,1,4,7,9,11}",5.938,9.667,20.167,0.361508 +"{0,4,7,8,9,11}",5.938,9.833,19.167,0.356296 +"{0,3,4,6,9,11}",5.950,9.833,22.833,0.347646 +"{0,3,4,6,7,10}",5.959,10.500,23.167,0.350423 +"{0,2,3,6,8,11}",5.959,10.667,23.333,0.323201 +"{0,4,6,7,8,10}",5.959,11.167,23.833,0.311164 +"{0,3,4,6,9,10}",5.962,10.500,25.167,0.342487 +"{0,1,4,6,9,10}",5.962,10.667,23.333,0.339312 +"{0,2,5,6,8,9}",5.962,10.667,23.333,0.337090 +"{0,2,3,5,8,10}",5.964,9.667,19.833,0.344101 +"{0,2,4,6,9,11}",5.968,8.667,18.833,0.385767 +"{0,2,4,5,7,10}",5.969,9.667,19.833,0.350767 +"{0,1,5,8,9,11}",5.976,10.667,21.333,0.330582 +"{0,1,3,7,8,9}",5.986,10.667,22.833,0.338757 +"{0,3,4,5,7,9}",5.992,9.833,20.333,0.348730 +"{0,1,4,5,7,11}",5.992,10.333,22.500,0.351534 +"{0,1,5,7,9,11}",5.992,10.667,22.833,0.335053 +"{0,1,3,5,6,10}",6.001,9.500,19.000,0.359841 +"{0,3,7,8,10,11}",6.001,10.333,19.500,0.345185 +"{0,3,4,6,7,9}",6.003,10.000,23.000,0.339312 +"{0,2,3,5,7,8}",6.004,9.667,19.167,0.339841 +"{0,1,5,8,9,10}",6.017,10.000,19.333,0.347963 +"{0,2,3,4,6,11}",6.019,10.333,21.333,0.320952 +"{0,1,3,6,8,9}",6.026,10.167,23.167,0.337090 +"{0,1,3,4,8,9}",6.030,10.167,20.833,0.340767 +"{0,2,3,6,7,9}",6.032,10.000,23.000,0.361534 +"{0,2,6,8,10,11}",6.032,11.000,23.833,0.306164 +"{0,4,6,8,9,10}",6.032,11.000,23.833,0.308942 +"{0,1,3,4,7,9}",6.045,10.500,23.167,0.337090 +"{0,2,3,5,7,9}",6.048,9.000,19.167,0.372989 +"{0,3,6,7,8,11}",6.054,10.500,21.167,0.335767 +"{0,1,2,6,9,10}",6.060,10.833,21.500,0.335582 +"{0,3,4,7,10,11}",6.061,10.167,20.833,0.369101 +"{0,2,3,4,8,11}",6.061,10.667,21.333,0.326138 +"{0,2,7,8,9,11}",6.064,10.667,22.167,0.347619 +"{0,1,5,6,8,9}",6.070,10.167,20.833,0.340767 +"{0,2,3,6,7,11}",6.072,10.000,20.667,0.351323 +"{0,3,6,7,9,11}",6.072,10.000,22.833,0.340979 +"{0,2,4,6,7,8}",6.072,11.000,23.167,0.317831 +"{0,2,3,8,10,11}",6.074,10.833,21.833,0.312063 +"{0,4,5,6,9,10}",6.075,10.833,23.000,0.334312 +"{0,3,5,8,9,10}",6.086,10.500,22.000,0.353175 +"{0,4,5,7,8,9}",6.089,10.333,19.500,0.341296 +"{0,2,4,6,10,11}",6.091,10.500,22.833,0.335053 +"{0,2,4,8,9,10}",6.092,10.833,23.167,0.324497 +"{0,1,3,4,5,7}",6.099,10.500,21.500,0.314841 +"{0,1,3,8,9,11}",6.099,10.667,21.667,0.312063 +"{0,2,6,7,9,10}",6.104,10.167,21.667,0.357619 +"{0,2,4,8,9,11}",6.108,10.000,21.500,0.365952 +"{0,2,3,4,7,8}",6.114,10.667,20.833,0.331138 +"{0,1,3,7,9,11}",6.114,10.833,23.667,0.317275 +"{0,2,5,6,9,11}",6.119,10.167,22.667,0.363201 +"{0,3,5,6,8,10}",6.123,9.333,19.500,0.350767 +"{0,2,3,4,6,8}",6.126,11.167,23.833,0.295608 +"{0,2,5,6,8,11}",6.129,10.667,25.333,0.324153 +"{0,1,2,4,6,10}",6.129,11.167,23.833,0.308942 +"{0,2,4,7,9,10}",6.136,9.167,19.333,0.370767 +"{0,1,4,6,8,10}",6.139,10.167,22.500,0.345053 +"{0,2,3,6,8,10}",6.139,10.333,22.667,0.329497 +"{0,3,5,6,8,9}",6.139,10.667,23.667,0.321534 +"{0,1,3,8,9,10}",6.140,10.833,22.333,0.341508 +"{0,1,4,7,8,9}",6.143,10.333,21.000,0.351878 +"{0,1,3,4,5,9}",6.143,10.833,21.500,0.317804 +"{0,2,4,6,7,10}",6.145,10.167,22.500,0.336164 +"{0,2,4,6,8,9}",6.145,10.167,22.500,0.340608 +"{0,2,3,6,9,11}",6.145,10.333,23.333,0.345423 +"{0,2,4,5,8,10}",6.146,10.500,22.833,0.318386 +"{0,1,3,5,8,11}",6.153,9.833,20.333,0.334841 +"{0,1,3,4,7,8}",6.153,10.000,20.667,0.349656 +"{0,3,5,6,7,9}",6.154,10.500,23.500,0.331905 +"{0,2,3,6,9,10}",6.157,10.167,23.000,0.352646 +"{0,3,4,8,9,11}",6.158,10.000,20.667,0.344656 +"{0,1,3,4,9,11}",6.158,10.500,21.500,0.318730 +"{0,2,4,5,8,11}",6.161,10.167,23.000,0.340979 +"{0,2,3,5,9,11}",6.161,10.500,23.333,0.338016 +"{0,3,5,7,8,11}",6.168,9.833,20.167,0.342249 +"{0,1,3,5,7,11}",6.168,10.500,22.833,0.326164 +"{0,2,3,5,8,11}",6.171,10.500,23.500,0.318757 +"{0,3,4,7,9,11}",6.174,9.333,19.667,0.368915 +"{0,2,3,4,7,11}",6.174,10.000,19.167,0.356296 +"{0,1,3,5,9,10}",6.184,10.167,21.667,0.355397 +"{0,4,6,7,10,11}",6.185,10.500,22.667,0.351534 +"{0,2,3,5,7,11}",6.187,10.167,21.667,0.345952 +"{0,2,3,7,10,11}",6.187,10.167,19.500,0.354074 +"{0,2,4,5,6,9}",6.188,10.167,20.667,0.348730 +"{0,1,2,4,9,10}",6.190,10.667,21.667,0.323730 +"{0,3,6,9,10,11}",6.198,11.000,24.000,0.327090 +"{0,1,4,5,7,10}",6.199,10.000,23.000,0.354868 +"{0,1,3,6,7,9}",6.208,10.667,25.333,0.331376 +"{0,1,3,5,7,10}",6.209,9.000,19.167,0.377434 +"{0,3,6,8,9,11}",6.224,10.667,23.667,0.318757 +"{0,2,3,5,6,9}",6.227,10.500,23.500,0.339312 +"{0,5,6,7,9,10}",6.227,10.833,22.333,0.334841 +"{0,2,5,9,10,11}",6.233,10.833,21.833,0.340952 +"{0,1,3,5,6,9}",6.236,10.333,23.167,0.325979 +"{0,1,2,6,8,10}",6.236,11.000,23.167,0.324497 +"{0,2,3,4,6,7}",6.239,10.667,21.667,0.314841 +"{0,2,3,4,8,10}",6.240,11.000,23.167,0.315608 +"{0,1,2,4,6,9}",6.242,9.833,20.333,0.342063 +"{0,2,6,8,9,11}",6.242,10.500,23.333,0.338016 +"{0,4,6,7,9,10}",6.242,10.500,23.333,0.336349 +"{0,2,4,5,6,10}",6.242,11.167,23.333,0.311164 +"{0,1,4,5,9,10}",6.243,10.167,20.833,0.349656 +"{0,2,5,8,10,11}",6.243,10.833,23.833,0.315794 +"{0,1,3,4,6,9}",6.252,10.333,23.333,0.321534 +"{0,4,5,6,8,10}",6.252,10.833,23.167,0.311164 +"{0,2,6,7,8,10}",6.252,11.000,23.333,0.315608 +"{0,1,4,8,9,11}",6.256,10.167,19.333,0.354074 +"{0,2,5,7,10,11}",6.259,10.500,22.000,0.357063 +"{0,3,4,5,7,8}",6.266,10.333,19.667,0.332407 +"{0,1,5,7,8,11}",6.266,10.500,22.667,0.344868 +"{0,4,5,7,8,10}",6.269,10.500,22.000,0.337619 +"{0,3,4,5,9,11}",6.271,10.667,22.833,0.338201 +"{0,1,2,5,6,10}",6.280,10.833,21.000,0.333360 +"{0,3,7,8,9,11}",6.281,10.500,21.333,0.326138 +"{0,3,4,7,9,10}",6.284,10.333,22.833,0.365979 +"{0,3,6,7,10,11}",6.293,10.333,21.000,0.357989 +"{0,2,5,7,8,10}",6.297,9.667,19.833,0.344101 +"{0,2,3,7,9,11}",6.300,10.167,21.167,0.365952 +"{0,1,4,5,8,11}",6.309,10.333,21.000,0.357989 +"{0,3,4,5,8,9}",6.309,10.667,21.333,0.336323 +"{0,2,6,7,10,11}",6.311,10.167,21.000,0.348360 +"{0,1,4,7,9,10}",6.312,10.500,23.500,0.345979 +"{0,4,5,8,9,10}",6.312,10.500,21.333,0.333360 +"{0,3,4,6,8,9}",6.321,10.333,23.167,0.325979 +"{0,1,3,6,9,11}",6.321,10.667,23.667,0.315794 +"{0,1,4,7,8,10}",6.322,10.167,23.000,0.352646 +"{0,1,3,7,9,10}",6.322,10.667,23.500,0.349683 +"{0,2,5,6,7,9}",6.340,10.500,22.000,0.366508 +"{0,1,2,5,9,10}",6.341,10.333,19.500,0.347963 +"{0,4,7,9,10,11}",6.343,10.167,20.667,0.349841 +"{0,2,3,6,7,8}",6.346,11.000,23.167,0.325423 +"{0,1,2,5,8,10}",6.351,10.167,20.667,0.339841 +"{0,2,3,4,7,10}",6.353,10.167,20.667,0.348730 +"{0,1,3,6,7,10}",6.359,10.500,23.000,0.365979 +"{0,2,3,6,10,11}",6.365,10.500,21.333,0.330582 +"{0,2,3,4,6,10}",6.365,11.000,23.833,0.311164 +"{0,2,4,5,6,8}",6.365,11.167,24.000,0.295608 +"{0,5,6,8,9,11}",6.365,11.333,24.333,0.311534 +"{0,1,4,8,9,10}",6.366,10.500,21.333,0.335582 +"{0,2,3,5,8,9}",6.366,10.667,23.167,0.343757 +"{0,1,2,4,8,10}",6.366,11.000,23.833,0.313386 +"{0,2,7,9,10,11}",6.372,10.333,19.833,0.345926 +"{0,1,4,7,8,11}",6.379,10.167,20.833,0.369101 +"{0,3,4,7,8,9}",6.379,10.333,21.000,0.347434 +"{0,4,6,8,9,11}",6.380,10.333,21.333,0.345952 +"{0,2,6,9,10,11}",6.383,10.833,22.333,0.334286 +"{0,1,3,7,8,11}",6.388,10.167,21.000,0.343915 +"{0,1,3,4,7,10}",6.391,10.500,23.500,0.354868 +"{0,3,7,8,9,10}",6.391,11.000,22.000,0.339286 +"{0,1,2,5,6,9}",6.393,10.500,21.167,0.347434 +"{0,3,4,5,7,11}",6.394,10.167,21.000,0.346138 +"{0,4,5,7,10,11}",6.397,10.833,23.333,0.358201 +"{0,1,3,4,6,10}",6.403,10.667,23.500,0.336349 +"{0,1,2,5,6,8}",6.403,10.833,23.000,0.325423 +"{0,2,3,4,6,9}",6.409,10.500,23.500,0.331905 +"{0,1,2,4,5,10}",6.410,10.833,21.833,0.310397 +"{0,2,4,9,10,11}",6.416,10.667,21.333,0.344656 +"{0,1,3,4,5,8}",6.417,10.333,19.500,0.332407 +"{0,1,4,6,8,9}",6.419,9.833,20.167,0.347249 +"{0,2,3,6,7,10}",6.419,9.833,20.167,0.360582 +"{0,1,2,4,6,8}",6.419,10.667,23.000,0.317831 +"{0,1,4,5,8,10}",6.420,9.667,20.000,0.358360 +"{0,2,4,5,8,9}",6.425,10.000,20.333,0.353915 +"{0,1,3,4,6,8}",6.428,10.333,21.833,0.333175 +"{0,1,3,4,8,11}",6.432,10.167,19.500,0.345185 +"{0,3,5,6,9,11}",6.434,11.333,26.000,0.324153 +"{0,3,4,8,9,10}",6.435,11.167,24.000,0.338757 +"{0,3,5,6,8,11}",6.444,10.833,23.333,0.320979 +"{0,3,6,7,8,9}",6.444,11.167,24.167,0.318757 +"{0,1,6,7,9,10}",6.447,11.333,24.333,0.332090 +"{0,1,3,4,7,11}",6.448,10.333,21.167,0.339471 +"{0,1,4,7,10,11}",6.451,10.833,23.833,0.349312 +"{0,2,3,8,9,11}",6.451,11.333,24.333,0.324868 +"{0,6,7,8,10,11}",6.459,11.500,22.500,0.302619 +"{0,1,5,7,9,10}",6.464,10.333,21.333,0.355397 +"{0,1,3,6,10,11}",6.472,11.000,22.000,0.336508 +"{0,2,5,6,7,11}",6.478,10.833,23.333,0.351534 +"{0,2,5,7,8,9}",6.479,10.500,21.000,0.359841 +"{0,2,3,5,10,11}",6.479,11.167,22.667,0.325397 +"{0,1,3,6,8,11}",6.498,10.667,22.167,0.341508 +"{0,3,4,5,7,10}",6.504,10.667,22.167,0.362063 +"{0,3,8,9,10,11}",6.504,11.833,23.333,0.300397 +"{0,1,2,6,8,9}",6.516,10.833,23.000,0.338757 +"{0,1,4,5,6,10}",6.516,11.000,23.167,0.334312 +"{0,3,7,9,10,11}",6.520,11.000,22.000,0.336508 +"{0,4,6,9,10,11}",6.522,11.167,24.000,0.337460 +"{0,1,2,4,5,9}",6.523,10.333,19.667,0.341296 +"{0,2,8,9,10,11}",6.523,12.000,23.667,0.290767 +"{0,1,4,6,7,9}",6.532,10.500,23.000,0.343757 +"{0,4,5,6,8,9}",6.532,10.833,21.667,0.317804 +"{0,2,3,5,6,11}",6.532,11.333,24.333,0.311534 +"{0,5,7,8,9,10}",6.533,10.833,20.333,0.322037 +"{0,1,3,4,9,10}",6.533,11.333,24.333,0.332090 +"{0,4,5,9,10,11}",6.538,11.333,23.333,0.342090 +"{0,1,3,4,8,10}",6.543,10.333,21.333,0.357619 +"{0,3,4,5,8,11}",6.545,10.500,21.167,0.335767 +"{0,1,7,8,9,11}",6.545,11.333,22.333,0.315952 +"{0,5,6,7,9,11}",6.547,11.000,23.500,0.333016 +"{0,2,4,5,7,8}",6.548,10.667,21.667,0.333175 +"{0,4,7,8,9,10}",6.548,11.167,22.667,0.321508 +"{0,3,5,9,10,11}",6.548,11.667,24.500,0.330794 +"{0,1,3,6,7,8}",6.551,11.000,23.500,0.343201 +"{0,1,3,4,6,11}",6.557,11.000,22.500,0.323175 +"{0,3,5,8,10,11}",6.558,10.667,21.167,0.341508 +"{0,1,2,4,7,11}",6.564,10.667,21.667,0.349841 +"{0,3,4,9,10,11}",6.564,11.333,23.833,0.335423 +"{0,2,3,5,6,8}",6.570,11.000,23.833,0.305238 +"{0,3,5,7,10,11}",6.574,10.833,21.833,0.363730 +"{0,1,2,4,7,10}",6.577,10.667,23.667,0.329683 +"{0,2,7,8,9,10}",6.577,11.167,21.833,0.325212 +"{0,4,5,6,9,11}",6.591,11.000,23.000,0.351534 +"{0,1,2,4,7,9}",6.592,10.500,22.000,0.359841 +"{0,2,3,9,10,11}",6.592,11.333,22.333,0.320397 +"{0,1,4,9,10,11}",6.592,11.667,23.167,0.320397 +"{0,4,5,6,8,11}",6.601,11.333,24.167,0.329312 +"{0,1,3,9,10,11}",6.602,12.000,23.667,0.295212 +"{0,2,5,6,10,11}",6.604,11.333,24.167,0.331534 +"{0,1,3,4,6,7}",6.611,11.333,24.333,0.316534 +"{0,1,3,8,10,11}",6.612,10.500,20.000,0.334815 +"{0,1,3,7,10,11}",6.627,11.000,22.500,0.340952 +"{0,1,4,5,6,9}",6.629,10.667,21.333,0.336323 +"{0,1,2,5,8,9}",6.630,10.500,21.167,0.351878 +"{0,2,3,8,9,10}",6.630,11.333,23.833,0.331349 +"{0,1,2,8,9,10}",6.630,11.500,22.500,0.314286 +"{0,2,3,4,10,11}",6.633,11.333,22.333,0.315952 +"{0,2,4,5,10,11}",6.636,11.333,23.833,0.330794 +"{0,1,4,5,6,8}",6.639,10.500,21.333,0.331138 +"{0,3,5,6,7,10}",6.639,10.500,21.000,0.362063 +"{0,1,4,6,9,11}",6.645,10.333,20.833,0.357063 +"{0,4,5,6,7,9}",6.645,11.000,22.000,0.330397 +"{0,6,7,9,10,11}",6.645,11.167,22.667,0.313730 +"{0,1,2,4,8,9}",6.646,10.333,21.167,0.344471 +"{0,1,2,4,5,7}",6.646,11.000,22.500,0.321508 +"{0,3,4,5,9,10}",6.646,11.167,23.667,0.352090 +"{0,5,8,9,10,11}",6.646,11.833,23.333,0.295952 +"{0,2,4,7,8,9}",6.661,10.833,21.833,0.364286 +"{0,5,7,9,10,11}",6.661,11.167,22.333,0.331323 +"{0,4,8,9,10,11}",6.661,11.833,23.333,0.311508 +"{0,3,5,6,7,8}",6.664,10.833,21.333,0.314841 +"{0,6,7,8,9,11}",6.670,11.333,22.333,0.302619 +"{0,2,3,7,8,9}",6.671,11.167,23.167,0.352090 +"{0,5,7,8,10,11}",6.671,11.167,22.167,0.323175 +"{0,5,6,9,10,11}",6.673,11.833,24.833,0.319868 +"{0,1,3,6,7,11}",6.680,11.333,24.167,0.333757 +"{0,5,6,8,10,11}",6.683,11.167,23.167,0.310794 +"{0,1,4,6,7,10}",6.683,11.333,26.000,0.342487 +"{0,1,5,6,7,9}",6.683,11.333,24.167,0.334312 +"{0,2,3,6,8,9}",6.683,11.500,26.167,0.331376 +"{0,6,7,8,9,10}",6.683,12.167,23.833,0.284656 +"{0,2,4,5,6,11}",6.688,10.833,22.833,0.333016 +"{0,3,4,5,6,9}",6.698,11.333,24.333,0.314312 +"{0,1,2,4,5,8}",6.699,10.667,21.500,0.320026 +"{0,1,2,4,9,11}",6.705,10.333,19.833,0.345926 +"{0,2,3,5,6,10}",6.711,10.667,21.667,0.337619 +"{0,1,3,4,5,11}",6.712,11.333,22.333,0.302619 +"{0,4,5,8,10,11}",6.715,11.333,23.667,0.333757 +"{0,5,6,7,8,11}",6.724,11.667,24.167,0.310979 +"{0,1,6,9,10,11}",6.727,11.833,23.333,0.309286 +"{0,2,3,4,7,9}",6.730,10.333,20.833,0.366508 +"{0,5,6,7,8,10}",6.736,11.167,21.833,0.311878 +"{0,1,5,9,10,11}",6.743,11.833,23.333,0.313730 +"{0,3,5,6,10,11}",6.752,11.333,23.333,0.340423 +"{0,5,6,7,8,9}",6.752,12.000,23.500,0.298730 +"{0,1,5,8,10,11}",6.753,10.500,21.000,0.336508 +"{0,1,7,8,9,10}",6.753,11.500,22.500,0.316508 +"{0,1,3,5,6,7}",6.762,11.000,23.000,0.320238 +"{0,1,6,7,9,11}",6.767,10.833,22.833,0.330794 +"{0,4,6,7,8,9}",6.767,11.167,22.167,0.317063 +"{0,1,4,8,10,11}",6.769,11.000,22.000,0.340952 +"{0,1,5,7,10,11}",6.769,11.167,24.000,0.341905 +"{0,1,2,4,7,8}",6.769,11.333,24.167,0.336534 +"{0,4,5,6,7,11}",6.783,11.333,23.333,0.339868 +"{0,1,6,7,8,10}",6.790,11.333,23.833,0.338016 +"{0,3,5,6,7,11}",6.793,11.333,23.667,0.329312 +"{0,1,5,6,9,11}",6.796,11.333,23.667,0.331534 +"{0,1,2,4,5,6}",6.796,11.500,22.500,0.289841 +"{0,1,4,6,10,11}",6.796,11.667,24.500,0.341905 +"{0,2,6,7,8,9}",6.796,11.833,24.667,0.333571 +"{0,6,8,9,10,11}",6.796,12.167,23.833,0.277434 +"{0,1,5,6,8,11}",6.806,11.167,23.667,0.340423 +"{0,1,6,7,8,9}",6.806,11.667,24.167,0.320423 +"{0,1,4,5,10,11}",6.812,11.500,24.000,0.335423 +"{0,1,4,6,8,11}",6.821,10.833,21.833,0.363730 +"{0,1,3,5,10,11}",6.822,11.000,21.667,0.329101 +"{0,3,4,5,8,10}",6.822,11.000,22.000,0.348730 +"{0,2,5,6,7,10}",6.824,11.000,22.000,0.348730 +"{0,1,5,6,7,10}",6.834,11.167,23.167,0.352090 +"{0,1,4,6,7,11}",6.837,11.000,23.000,0.358201 +"{0,1,3,4,10,11}",6.838,11.333,22.333,0.322619 +"{0,1,2,6,9,11}",6.840,10.500,21.000,0.340952 +"{0,2,3,4,9,11}",6.843,10.833,21.833,0.347619 +"{0,2,5,6,7,8}",6.849,11.667,24.500,0.313571 +"{0,1,2,5,9,11}",6.856,11.000,22.000,0.334286 +"{0,2,3,4,9,10}",6.856,11.000,23.000,0.338016 +"{0,1,2,4,10,11}",6.856,12.000,23.667,0.301878 +"{0,1,5,6,7,8}",6.859,11.500,23.500,0.329312 +"{0,1,2,5,8,11}",6.866,11.167,24.167,0.327090 +"{0,1,4,6,7,8}",6.875,11.333,23.667,0.336534 +"{0,3,4,5,6,8}",6.875,11.333,22.833,0.297063 +"{0,1,3,5,6,11}",6.875,11.500,24.000,0.310794 +"{0,1,2,4,8,11}",6.882,11.000,22.500,0.336508 +"{0,1,2,5,7,11}",6.882,11.500,24.333,0.337460 +"{0,1,7,9,10,11}",6.882,12.000,23.667,0.301878 +"{0,1,7,8,10,11}",6.891,11.333,22.833,0.322619 +"{0,1,2,6,7,9}",6.893,11.167,23.667,0.352090 +"{0,1,2,5,7,10}",6.894,10.500,21.000,0.353175 +"{0,1,6,8,10,11}",6.903,11.333,22.500,0.329101 +"{0,1,2,4,6,11}",6.909,10.833,21.500,0.331323 +"{0,1,2,5,7,9}",6.910,10.833,21.833,0.364286 +"{0,1,6,8,9,11}",6.919,11.167,22.167,0.325397 +"{0,2,3,5,6,7}",6.919,11.167,22.167,0.321508 +"{0,1,6,7,10,11}",6.919,11.833,24.833,0.335423 +"{0,1,2,5,7,8}",6.920,11.167,23.167,0.343201 +"{0,2,3,4,5,9}",6.925,10.833,21.333,0.330397 +"{0,1,2,4,5,11}",6.925,11.333,22.333,0.313730 +"{0,1,2,3,6,9}",6.947,11.333,24.333,0.318757 +"{0,1,5,6,10,11}",6.947,11.667,23.667,0.335423 +"{0,2,3,4,5,7}",6.951,10.833,20.333,0.315370 +"{0,7,8,9,10,11}",6.951,12.333,22.333,0.275926 +"{0,1,2,4,6,7}",6.962,11.333,23.833,0.320238 +"{0,4,5,6,7,10}",6.962,11.833,24.667,0.324683 +"{0,1,2,3,5,9}",6.964,11.167,22.667,0.317063 +"{0,1,2,3,5,8}",6.973,11.167,22.167,0.314841 +"{0,1,2,7,10,11}",6.979,11.833,23.333,0.320397 +"{0,4,5,6,7,8}",6.988,12.167,23.667,0.294286 +"{0,1,2,3,5,7}",6.989,11.000,21.667,0.314101 +"{0,1,2,6,10,11}",6.991,12.000,23.500,0.313730 +"{0,1,2,7,9,11}",6.995,11.167,22.333,0.344656 +"{0,3,4,5,6,11}",7.003,12.000,25.000,0.310979 +"{0,1,2,7,8,11}",7.004,11.833,24.833,0.335423 +"{0,1,2,7,9,10}",7.007,11.167,22.167,0.341508 +"{0,1,2,5,10,11}",7.007,12.000,23.500,0.309286 +"{0,1,2,6,8,11}",7.016,11.333,24.167,0.330794 +"{0,3,4,5,6,10}",7.016,11.667,24.500,0.324683 +"{0,1,2,7,8,10}",7.017,11.167,23.167,0.331349 +"{0,1,2,6,7,11}",7.032,11.500,23.500,0.342090 +"{0,5,6,7,10,11}",7.032,11.833,24.333,0.333201 +"{0,1,8,9,10,11}",7.033,12.333,22.333,0.282593 +"{0,1,2,6,7,10}",7.044,11.500,23.833,0.338757 +"{0,2,3,4,8,9}",7.048,11.500,23.833,0.334312 +"{0,3,4,5,6,7}",7.057,12.000,23.500,0.296508 +"{0,1,2,5,6,11}",7.060,11.667,24.167,0.319868 +"{0,2,3,4,5,11}",7.064,11.500,23.000,0.302619 +"{0,1,2,3,7,11}",7.074,12.000,23.500,0.311508 +"{0,2,4,5,6,7}",7.075,11.500,22.667,0.314101 +"{0,4,5,6,10,11}",7.075,12.167,25.833,0.331931 +"{0,1,2,3,6,11}",7.085,12.000,23.500,0.295952 +"{0,1,4,5,6,7}",7.085,12.000,25.000,0.313757 +"{0,1,2,3,7,10}",7.086,10.833,21.333,0.339286 +"{0,1,3,4,5,10}",7.086,11.167,22.167,0.334841 +"{0,1,2,3,6,10}",7.098,11.333,22.333,0.321508 +"{0,1,2,3,7,9}",7.102,11.500,24.333,0.333571 +"{0,2,3,4,5,8}",7.102,11.500,22.500,0.297063 +"{0,1,2,3,5,11}",7.102,12.167,23.833,0.277434 +"{0,1,6,7,8,11}",7.111,11.833,24.333,0.337646 +"{0,1,2,3,7,8}",7.112,11.667,23.667,0.329312 +"{0,1,2,3,5,10}",7.115,10.833,20.333,0.322037 +"{0,3,4,5,10,11}",7.117,12.000,24.500,0.337646 +"{0,1,2,9,10,11}",7.120,12.333,22.333,0.291481 +"{0,1,2,3,6,8}",7.123,11.833,24.667,0.313571 +"{0,1,2,8,10,11}",7.130,12.167,23.833,0.295212 +"{0,1,3,4,5,6}",7.139,11.667,23.167,0.285397 +"{0,1,2,3,6,7}",7.139,11.833,24.333,0.313757 +"{0,1,2,8,9,11}",7.146,11.500,23.000,0.320397 +"{0,1,5,6,7,11}",7.154,12.000,25.667,0.331931 +"{0,1,2,3,5,6}",7.167,11.667,22.667,0.285397 +"{0,1,2,3,4,7}",7.171,12.000,23.500,0.296508 +"{0,1,2,3,4,6}",7.183,12.167,23.833,0.266878 +"{0,1,4,5,6,11}",7.198,12.000,24.500,0.333201 +"{0,1,2,7,8,9}",7.199,12.000,24.500,0.342646 +"{0,1,2,3,9,11}",7.215,12.167,23.833,0.290767 +"{0,1,2,3,8,11}",7.225,12.000,23.500,0.300397 +"{0,1,2,3,9,10}",7.228,11.667,23.167,0.316508 +"{0,1,2,6,7,8}",7.236,12.167,25.833,0.328042 +"{0,1,2,3,8,10}",7.238,11.500,22.667,0.325212 +"{0,2,3,4,5,10}",7.243,11.667,22.833,0.311878 +"{0,1,2,3,8,9}",7.253,12.167,25.167,0.320423 +"{0,1,2,5,6,7}",7.280,12.000,24.500,0.324868 +"{0,2,3,4,5,6}",7.296,12.333,24.000,0.266878 +"{0,1,2,3,4,9}",7.312,12.000,23.500,0.298730 +"{0,1,2,3,4,8}",7.322,12.167,23.667,0.294286 +"{0,1,2,3,10,11}",7.366,12.500,22.500,0.282593 +"{0,1,2,3,4,11}",7.451,12.333,22.333,0.275926 +"{0,1,2,3,4,10}",7.464,12.333,24.000,0.284656 +"{0,1,2,3,4,5}",7.533,12.500,22.500,0.254259 +"{0,2,4,5,7,9,11}",5.701,9.286,19.000,0.382559 +"{0,2,4,6,7,9,11}",5.830,9.286,19.000,0.382559 +"{0,2,3,5,7,9,10}",5.863,9.571,19.286,0.371844 +"{0,3,4,6,8,10,11}",5.868,10.714,22.429,0.340514 +"{0,2,4,7,8,10,11}",5.874,11.000,23.143,0.338927 +"{0,2,5,6,8,9,10}",5.895,11.143,23.286,0.328212 +"{0,2,4,6,7,8,11}",5.919,10.714,22.429,0.348450 +"{0,1,4,5,7,9,11}",5.933,10.571,22.286,0.351625 +"{0,4,5,7,8,9,11}",5.933,10.571,21.286,0.344728 +"{0,1,3,5,7,8,9}",5.975,10.857,22.571,0.340911 +"{0,1,5,6,8,9,10}",5.987,10.714,21.429,0.338776 +"{0,2,4,5,7,9,10}",5.998,9.857,19.571,0.363908 +"{0,3,4,7,8,10,11}",6.001,10.714,21.143,0.353193 +"{0,1,3,5,7,8,10}",6.023,9.571,19.286,0.371844 +"{0,2,4,6,8,10,11}",6.027,11.286,25.000,0.324660 +"{0,1,3,5,6,8,10}",6.033,9.714,19.429,0.363908 +"{0,3,4,6,7,8,11}",6.046,10.714,21.143,0.342082 +"{0,2,4,6,8,9,10}",6.051,11.429,25.143,0.321882 +"{0,3,4,6,7,8,10}",6.057,11.143,23.286,0.334562 +"{0,1,3,5,8,9,11}",6.071,11.143,23.286,0.326228 +"{0,1,3,5,7,9,11}",6.085,11.286,25.000,0.324660 +"{0,2,4,5,6,9,10}",6.089,11.000,22.714,0.340911 +"{0,2,3,4,7,8,11}",6.098,10.714,21.143,0.348432 +"{0,2,3,4,6,8,11}",6.108,11.000,23.143,0.327816 +"{0,1,4,5,7,8,9}",6.109,10.857,21.286,0.345654 +"{0,2,3,7,8,10,11}",6.109,10.857,21.571,0.338379 +"{0,2,5,7,8,9,11}",6.125,10.857,23.571,0.348450 +"{0,1,2,4,6,9,10}",6.135,11.143,23.286,0.334562 +"{0,2,3,5,7,8,10}",6.158,10.000,19.714,0.352797 +"{0,1,3,4,5,7,9}",6.168,11.143,23.286,0.329800 +"{0,1,3,6,8,9,10}",6.176,11.000,23.714,0.344085 +"{0,2,4,5,7,8,11}",6.184,10.714,23.286,0.350567 +"{0,2,3,4,6,7,11}",6.205,10.571,21.286,0.344728 +"{0,1,4,5,8,9,11}",6.206,10.857,21.286,0.351606 +"{0,2,4,6,7,8,10}",6.216,11.571,25.286,0.315533 +"{0,3,5,7,8,9,11}",6.228,11.143,23.429,0.327816 +"{0,2,4,5,6,8,10}",6.240,11.571,25.286,0.307596 +"{0,3,4,6,7,10,11}",6.251,11.000,23.143,0.353874 +"{0,4,6,7,8,10,11}",6.251,11.571,23.714,0.327419 +"{0,2,3,4,7,8,10}",6.252,11.000,22.714,0.337736 +"{0,1,3,4,5,7,8}",6.260,10.714,21.429,0.335601 +"{0,2,3,6,8,10,11}",6.262,11.143,23.429,0.326228 +"{0,2,3,4,6,8,10}",6.262,11.429,25.143,0.315533 +"{0,1,2,5,6,9,10}",6.265,11.000,21.429,0.347241 +"{0,2,4,6,7,10,11}",6.267,10.571,22.429,0.351625 +"{0,1,2,5,6,8,10}",6.273,11.000,22.714,0.336149 +"{0,3,5,6,8,9,10}",6.273,11.143,23.857,0.334562 +"{0,3,4,5,7,9,11}",6.278,10.571,22.429,0.348450 +"{0,2,5,6,8,9,11}",6.278,11.286,25.429,0.336961 +"{0,2,4,7,8,9,11}",6.281,10.714,22.000,0.368405 +"{0,3,5,6,7,9,10}",6.286,11.000,23.714,0.347260 +"{0,1,4,6,8,9,10}",6.286,11.143,23.429,0.334562 +"{0,1,2,4,6,8,10}",6.286,11.429,25.143,0.321882 +"{0,3,4,6,7,9,11}",6.289,10.429,23.000,0.350567 +"{0,2,4,5,8,9,10}",6.292,11.000,22.857,0.336149 +"{0,2,3,6,7,8,11}",6.297,11.286,23.429,0.336413 +"{0,1,3,4,5,8,9}",6.298,11.000,21.429,0.337717 +"{0,3,4,6,7,9,10}",6.300,11.286,25.429,0.343707 +"{0,2,4,5,8,9,11}",6.305,10.714,23.286,0.356916 +"{0,1,3,4,8,9,11}",6.311,10.714,21.429,0.338379 +"{0,1,4,5,7,8,11}",6.311,11.000,23.143,0.353874 +"{0,1,5,7,8,9,11}",6.311,11.571,23.714,0.329006 +"{0,2,3,6,7,9,11}",6.313,10.714,23.286,0.356916 +"{0,2,3,5,8,9,11}",6.314,11.429,25.571,0.332200 +"{0,1,3,5,7,8,11}",6.320,10.571,22.429,0.340514 +"{0,2,3,6,7,9,10}",6.324,10.714,23.286,0.358900 +"{0,1,3,4,7,9,11}",6.324,10.857,23.143,0.338927 +"{0,2,3,5,7,9,11}",6.327,10.571,23.000,0.356255 +"{0,1,3,6,7,9,10}",6.332,11.429,25.571,0.345295 +"{0,1,3,5,8,9,10}",6.333,10.857,22.143,0.356104 +"{0,2,3,5,7,8,11}",6.335,10.857,23.429,0.337868 +"{0,3,6,7,8,10,11}",6.343,11.143,21.857,0.336791 +"{0,2,3,5,6,9,10}",6.348,10.857,23.429,0.350964 +"{0,2,3,4,8,10,11}",6.349,11.571,23.714,0.324244 +"{0,1,3,5,6,9,10}",6.356,10.857,23.429,0.349376 +"{0,1,3,4,7,8,9}",6.357,11.143,23.286,0.341572 +"{0,2,6,7,8,10,11}",6.359,11.571,23.857,0.321070 +"{0,1,3,4,6,9,10}",6.370,11.429,25.571,0.337358 +"{0,1,2,6,8,9,10}",6.370,11.714,23.857,0.327816 +"{0,1,2,4,5,9,10}",6.376,10.857,21.571,0.338776 +"{0,2,5,8,9,10,11}",6.376,11.714,24.429,0.321070 +"{0,1,3,5,6,8,9}",6.378,11.000,23.571,0.335091 +"{0,4,5,6,8,9,10}",6.383,11.571,23.857,0.315117 +"{0,2,5,7,9,10,11}",6.389,10.857,21.857,0.355178 +"{0,3,4,6,9,10,11}",6.396,11.571,25.714,0.339739 +"{0,2,5,7,8,10,11}",6.397,11.286,24.000,0.332577 +"{0,2,4,7,9,10,11}",6.402,10.429,21.000,0.363114 +"{0,1,4,7,8,9,11}",6.408,10.857,21.571,0.351077 +"{0,3,4,5,8,9,11}",6.408,11.286,23.429,0.336413 +"{0,1,3,4,5,9,11}",6.408,11.571,23.714,0.321070 +"{0,1,3,7,8,9,11}",6.416,11.429,23.714,0.324244 +"{0,3,4,6,8,9,11}",6.418,10.857,23.429,0.337868 +"{0,3,4,6,8,9,10}",6.429,11.429,25.429,0.328893 +"{0,1,3,6,9,10,11}",6.429,11.714,24.429,0.324244 +"{0,2,4,6,8,9,11}",6.434,10.571,23.000,0.356255 +"{0,2,5,6,7,9,11}",6.434,10.857,23.143,0.361149 +"{0,2,3,5,9,10,11}",6.435,11.571,24.000,0.336943 +"{0,2,3,4,6,7,8}",6.440,11.714,23.857,0.313530 +"{0,2,3,5,8,10,11}",6.443,11.286,24.000,0.327816 +"{0,2,5,6,7,9,10}",6.445,11.000,22.286,0.354516 +"{0,2,3,4,7,10,11}",6.446,10.857,21.571,0.351077 +"{0,2,4,5,7,10,11}",6.448,11.000,23.286,0.353212 +"{0,2,3,6,7,8,10}",6.451,11.000,22.857,0.337736 +"{0,3,4,5,7,8,9}",6.454,11.143,21.857,0.334014 +"{0,2,3,4,6,10,11}",6.456,11.286,23.571,0.329006 +"{0,2,3,5,7,10,11}",6.457,11.000,22.286,0.352532 +"{0,2,4,6,7,9,10}",6.458,10.714,23.143,0.350302 +"{0,3,4,7,8,9,11}",6.467,10.571,21.143,0.348432 +"{0,1,4,5,6,9,10}",6.467,11.429,23.571,0.339985 +"{0,1,2,5,8,9,10}",6.468,11.000,21.714,0.341950 +"{0,2,3,5,8,9,10}",6.468,11.143,23.429,0.345673 +"{0,1,4,5,6,8,10}",6.475,10.857,22.714,0.340911 +"{0,2,4,5,6,8,9}",6.480,11.286,23.571,0.329800 +"{0,2,3,5,6,9,11}",6.480,11.571,25.714,0.336961 +"{0,1,4,5,7,9,10}",6.481,10.857,23.429,0.349376 +"{0,1,2,4,8,9,10}",6.481,11.429,23.714,0.327816 +"{0,2,3,5,6,8,11}",6.488,11.571,25.714,0.317914 +"{0,3,6,8,9,10,11}",6.488,11.857,24.571,0.314720 +"{0,1,3,5,7,9,10}",6.489,10.714,23.143,0.356652 +"{0,1,4,5,7,8,10}",6.489,10.714,23.286,0.350964 +"{0,2,3,4,6,9,11}",6.493,10.857,23.571,0.348450 +"{0,2,3,6,7,10,11}",6.502,10.714,21.286,0.351606 +"{0,1,3,4,6,9,11}",6.502,11.000,23.714,0.332577 +"{0,2,3,4,6,7,10}",6.502,11.143,23.429,0.334562 +"{0,3,6,7,9,10,11}",6.502,11.429,24.000,0.335884 +"{0,4,5,7,9,10,11}",6.507,11.143,23.000,0.348054 +"{0,1,3,4,7,8,10}",6.511,10.857,23.429,0.358900 +"{0,3,4,5,7,8,11}",6.513,10.714,21.286,0.342082 +"{0,1,2,5,6,8,9}",6.513,11.286,23.429,0.341572 +"{0,1,3,4,5,7,11}",6.513,11.286,23.571,0.327419 +"{0,2,6,7,9,10,11}",6.518,11.000,22.286,0.345786 +"{0,1,3,4,6,8,10}",6.521,10.714,23.143,0.350302 +"{0,1,2,4,6,8,9}",6.526,10.714,22.571,0.340911 +"{0,2,3,5,6,7,9}",6.526,11.143,23.857,0.344085 +"{0,1,4,5,8,9,10}",6.527,10.714,21.286,0.347241 +"{0,1,2,4,5,8,10}",6.527,11.143,23.429,0.328212 +"{0,1,3,4,6,8,9}",6.534,10.857,23.429,0.335091 +"{0,3,6,7,8,9,10}",6.534,11.857,24.571,0.324641 +"{0,2,3,4,6,7,9}",6.539,11.000,23.714,0.344085 +"{0,2,6,7,8,9,11}",6.539,11.571,24.000,0.338530 +"{0,2,5,6,9,10,11}",6.542,11.571,24.143,0.342234 +"{0,1,3,4,6,7,9}",6.548,11.429,25.571,0.327834 +"{0,3,5,6,8,9,11}",6.548,11.857,26.000,0.317914 +"{0,3,5,7,8,9,10}",6.549,11.143,22.143,0.346051 +"{0,2,5,6,8,10,11}",6.550,11.571,25.571,0.318972 +"{0,2,3,7,9,10,11}",6.553,11.143,22.000,0.348961 +"{0,1,4,7,9,10,11}",6.553,11.571,24.286,0.336943 +"{0,2,4,6,9,10,11}",6.555,11.143,23.571,0.349509 +"{0,1,3,4,7,8,11}",6.559,10.571,21.143,0.353193 +"{0,2,5,7,8,9,10}",6.564,11.000,21.571,0.341289 +"{0,2,3,6,9,10,11}",6.564,11.429,24.000,0.337472 +"{0,2,4,5,9,10,11}",6.569,11.429,23.286,0.349641 +"{0,2,3,7,8,9,11}",6.575,11.571,24.143,0.342234 +"{0,1,3,6,8,10,11}",6.580,11.000,22.000,0.347241 +"{0,2,3,6,8,9,11}",6.585,11.714,25.857,0.332200 +"{0,2,5,6,7,8,11}",6.585,11.857,26.000,0.328628 +"{0,1,3,6,8,9,11}",6.594,11.286,24.000,0.327816 +"{0,1,3,7,8,9,10}",6.595,11.714,24.143,0.342101 +"{0,3,4,5,7,9,10}",6.599,11.143,23.429,0.355197 +"{0,1,4,5,7,10,11}",6.599,11.571,25.714,0.347676 +"{0,1,3,6,7,9,11}",6.607,11.429,25.429,0.326909 +"{0,3,4,5,7,8,10}",6.608,11.143,22.429,0.348167 +"{0,1,4,6,7,9,10}",6.610,11.714,25.857,0.337358 +"{0,1,2,4,5,6,10}",6.610,11.857,24.000,0.315117 +"{0,2,4,5,6,9,11}",6.614,10.857,22.714,0.361149 +"{0,2,4,5,6,8,11}",6.623,11.429,25.429,0.328496 +"{0,2,4,5,7,8,10}",6.624,11.143,23.571,0.331255 +"{0,1,3,6,7,8,10}",6.626,11.143,23.429,0.358371 +"{0,1,3,4,6,10,11}",6.631,11.571,24.000,0.338530 +"{0,1,3,4,8,9,10}",6.632,11.571,24.143,0.342630 +"{0,4,6,7,9,10,11}",6.636,11.286,23.714,0.340117 +"{0,1,2,4,7,10,11}",6.637,11.571,24.286,0.336943 +"{0,2,7,8,9,10,11}",6.637,12.143,23.571,0.314701 +"{0,1,3,6,7,8,9}",6.640,11.857,26.000,0.329025 +"{0,2,3,5,6,8,10}",6.642,11.000,23.429,0.331255 +"{0,1,3,4,5,8,11}",6.643,11.000,21.714,0.336791 +"{0,2,3,5,7,8,9}",6.645,11.143,23.000,0.347260 +"{0,1,3,4,7,9,10}",6.645,11.714,25.857,0.345295 +"{0,3,5,8,9,10,11}",6.645,12.143,25.000,0.322657 +"{0,2,3,4,6,9,10}",6.647,11.429,25.429,0.336829 +"{0,2,6,8,9,10,11}",6.647,12.143,25.000,0.311017 +"{0,1,2,4,7,9,11}",6.650,10.714,21.714,0.363114 +"{0,1,3,4,6,8,11}",6.653,11.000,22.286,0.346183 +"{0,2,3,5,6,8,9}",6.656,11.857,26.000,0.327834 +"{0,4,6,7,8,9,11}",6.658,11.143,22.000,0.336262 +"{0,3,5,7,9,10,11}",6.659,11.571,24.000,0.343159 +"{0,4,5,7,8,10,11}",6.659,11.714,24.286,0.337472 +"{0,3,4,8,9,10,11}",6.659,12.000,24.571,0.329138 +"{0,1,2,4,7,9,10}",6.661,11.143,23.857,0.344085 +"{0,3,6,7,8,9,11}",6.666,11.571,24.143,0.320011 +"{0,3,5,7,8,10,11}",6.667,11.143,22.000,0.346183 +"{0,3,5,6,9,10,11}",6.669,12.143,26.286,0.331803 +"{0,3,4,7,9,10,11}",6.672,11.286,23.429,0.351757 +"{0,2,4,8,9,10,11}",6.675,12.143,25.000,0.326890 +"{0,3,5,6,8,10,11}",6.677,11.286,23.143,0.334165 +"{0,1,3,5,6,7,9}",6.677,11.571,25.571,0.325718 +"{0,1,3,4,6,7,10}",6.677,11.714,25.857,0.343707 +"{0,4,5,6,8,9,11}",6.682,11.714,24.286,0.332710 +"{0,4,5,7,8,9,10}",6.683,11.429,22.714,0.328723 +"{0,2,3,8,9,10,11}",6.683,12.286,25.000,0.314324 +"{0,1,3,4,5,7,10}",6.691,11.143,23.857,0.347260 +"{0,1,3,8,9,10,11}",6.691,12.143,23.571,0.313114 +"{0,4,5,6,7,9,11}",6.695,11.286,23.143,0.344879 +"{0,1,2,4,5,7,11}",6.696,11.429,23.857,0.340117 +"{0,3,5,6,7,9,11}",6.704,11.714,25.714,0.328496 +"{0,1,4,7,8,10,11}",6.705,11.429,24.000,0.346995 +"{0,3,4,7,8,9,10}",6.705,11.714,24.286,0.341043 +"{0,1,3,7,9,10,11}",6.705,12.143,25.000,0.322128 +"{0,1,2,4,5,6,9}",6.706,11.143,21.857,0.334014 +"{0,4,5,6,7,9,10}",6.706,11.714,24.143,0.329403 +"{0,1,4,6,9,10,11}",6.706,12.000,24.857,0.336943 +"{0,1,2,4,5,7,10}",6.707,11.143,23.857,0.334562 +"{0,1,3,7,8,10,11}",6.713,11.143,22.429,0.345786 +"{0,1,4,5,6,8,9}",6.715,11.000,21.571,0.337717 +"{0,1,2,4,5,6,8}",6.715,11.571,23.857,0.313530 +"{0,1,5,7,8,9,10}",6.716,11.286,22.143,0.338246 +"{0,1,2,4,5,7,9}",6.721,11.000,22.286,0.351342 +"{0,2,4,7,8,9,10}",6.721,11.571,24.000,0.335620 +"{0,2,4,5,8,10,11}",6.721,11.857,25.857,0.326909 +"{0,1,4,5,9,10,11}",6.721,12.000,24.571,0.335488 +"{0,3,5,6,7,8,10}",6.723,11.000,21.571,0.339701 +"{0,1,4,7,8,9,10}",6.729,11.571,24.143,0.334694 +"{0,2,3,7,8,9,10}",6.729,11.571,23.429,0.343689 +"{0,1,3,5,9,10,11}",6.729,12.143,25.000,0.322128 +"{0,1,3,5,8,10,11}",6.737,10.714,21.286,0.347241 +"{0,1,3,6,7,10,11}",6.737,11.714,24.286,0.346995 +"{0,3,5,6,7,8,9}",6.737,12.000,24.714,0.313530 +"{0,1,5,6,7,9,10}",6.739,11.714,24.286,0.339456 +"{0,2,3,6,8,9,10}",6.739,11.857,25.857,0.332067 +"{0,1,4,6,7,9,11}",6.741,10.857,22.714,0.353212 +"{0,1,3,4,9,10,11}",6.742,12.143,24.857,0.322260 +"{0,3,4,6,7,8,9}",6.750,11.571,24.143,0.321995 +"{0,1,3,4,8,10,11}",6.751,11.143,22.000,0.345786 +"{0,1,3,5,7,10,11}",6.751,11.286,23.714,0.346334 +"{0,2,3,5,6,10,11}",6.752,11.714,24.286,0.331122 +"{0,3,4,5,6,9,10}",6.752,12.000,26.143,0.333787 +"{0,5,6,8,9,10,11}",6.752,12.286,25.000,0.301625 +"{0,4,7,8,9,10,11}",6.756,12.000,23.286,0.321580 +"{0,1,2,4,9,10,11}",6.758,12.000,23.429,0.324225 +"{0,1,3,4,7,10,11}",6.764,11.429,24.000,0.346995 +"{0,3,7,8,9,10,11}",6.764,12.143,23.429,0.316818 +"{0,4,6,8,9,10,11}",6.766,12.143,25.000,0.317366 +"{0,1,2,4,5,8,9}",6.767,10.857,21.429,0.345654 +"{0,2,3,4,7,9,11}",6.769,10.714,21.571,0.368405 +"{0,1,3,5,6,9,11}",6.774,11.857,25.857,0.318972 +"{0,2,4,5,6,7,11}",6.779,11.286,23.143,0.344879 +"{0,2,3,4,7,9,10}",6.780,11.000,22.857,0.358371 +"{0,2,4,5,7,8,9}",6.780,11.143,22.000,0.351342 +"{0,4,5,8,9,10,11}",6.780,12.143,24.714,0.325964 +"{0,1,3,5,6,8,11}",6.783,11.286,23.571,0.334165 +"{0,2,3,5,6,7,11}",6.787,11.714,24.286,0.332710 +"{0,3,4,5,6,7,9}",6.787,11.857,24.571,0.318292 +"{0,5,6,7,8,9,11}",6.787,12.286,25.000,0.306387 +"{0,5,6,7,8,9,10}",6.798,12.286,23.714,0.302400 +"{0,1,2,4,7,8,11}",6.802,11.571,24.143,0.351757 +"{0,1,2,4,6,9,11}",6.803,10.571,21.143,0.355178 +"{0,2,4,5,6,7,9}",6.803,11.143,22.143,0.347638 +"{0,1,3,5,6,7,10}",6.807,11.143,23.000,0.355197 +"{0,1,3,4,6,7,11}",6.809,11.571,24.143,0.337472 +"{0,1,6,7,9,10,11}",6.812,12.143,24.857,0.319085 +"{0,4,6,7,8,9,10}",6.812,12.286,25.143,0.306652 +"{0,1,2,4,7,8,10}",6.813,11.571,25.571,0.332067 +"{0,1,5,8,9,10,11}",6.813,12.000,23.286,0.318405 +"{0,2,3,4,5,7,11}",6.815,11.143,22.429,0.336262 +"{0,1,2,4,5,9,11}",6.817,11.143,22.000,0.345786 +"{0,1,4,8,9,10,11}",6.826,12.000,23.286,0.326342 +"{0,1,5,7,9,10,11}",6.826,12.000,24.857,0.326890 +"{0,1,3,5,6,7,8}",6.828,11.429,23.286,0.330990 +"{0,1,5,7,8,10,11}",6.834,11.429,23.857,0.338530 +"{0,1,5,6,9,10,11}",6.836,12.143,24.714,0.327551 +"{0,2,6,7,8,9,10}",6.836,12.429,25.286,0.317763 +"{0,2,3,4,5,7,9}",6.839,10.857,21.429,0.347638 +"{0,1,3,4,6,7,8}",6.842,11.714,24.286,0.331519 +"{0,1,5,6,8,10,11}",6.844,11.429,23.286,0.338530 +"{0,1,6,7,8,9,10}",6.844,12.429,25.143,0.316308 +"{0,4,5,6,7,8,11}",6.847,12.143,24.714,0.324376 +"{0,1,2,4,6,7,9}",6.849,11.143,23.429,0.347260 +"{0,2,5,6,7,10,11}",6.849,11.714,24.286,0.343821 +"{0,3,5,6,7,8,11}",6.855,11.714,23.857,0.321599 +"{0,1,4,6,8,10,11}",6.858,11.571,24.000,0.346334 +"{0,1,5,6,8,9,11}",6.858,11.857,24.429,0.331122 +"{0,3,4,5,7,10,11}",6.861,11.714,24.286,0.353345 +"{0,1,4,6,8,9,11}",6.871,11.000,21.857,0.352532 +"{0,1,4,6,7,10,11}",6.871,12.000,26.143,0.347676 +"{0,1,4,5,8,10,11}",6.872,11.429,23.571,0.346995 +"{0,1,2,4,5,7,8}",6.872,11.714,24.286,0.331519 +"{0,3,4,5,8,9,10}",6.872,11.857,24.429,0.341043 +"{0,1,2,6,9,10,11}",6.874,12.000,23.286,0.324754 +"{0,2,5,6,7,8,10}",6.882,11.714,24.143,0.321334 +"{0,3,4,5,6,9,11}",6.884,12.143,26.286,0.328628 +"{0,5,7,8,9,10,11}",6.885,12.286,23.714,0.305178 +"{0,1,2,5,9,10,11}",6.888,12.000,23.286,0.324754 +"{0,1,5,6,7,8,10}",6.890,11.571,23.429,0.340514 +"{0,3,4,5,6,8,11}",6.893,11.857,24.429,0.321599 +"{0,2,5,6,7,8,9}",6.895,12.286,25.143,0.329403 +"{0,1,2,5,8,10,11}",6.896,11.857,24.571,0.324244 +"{0,2,3,4,8,9,11}",6.898,11.714,24.286,0.342234 +"{0,1,3,6,7,8,11}",6.901,11.714,24.286,0.340646 +"{0,3,4,5,6,8,10}",6.904,11.571,24.000,0.326096 +"{0,1,3,5,6,10,11}",6.904,11.714,23.571,0.338530 +"{0,1,4,6,7,8,10}",6.904,11.857,25.857,0.336829 +"{0,1,5,6,7,8,9}",6.904,12.286,24.857,0.323186 +"{0,1,2,6,7,9,11}",6.909,11.286,23.143,0.349641 +"{0,1,2,5,8,9,11}",6.909,11.571,24.143,0.337472 +"{0,2,4,6,7,8,9}",6.909,11.714,24.143,0.337207 +"{0,1,2,5,7,10,11}",6.909,12.000,24.857,0.336943 +"{0,1,2,4,8,10,11}",6.909,12.143,25.000,0.322128 +"{0,5,6,7,9,10,11}",6.909,12.143,24.857,0.322260 +"{0,1,4,6,7,8,9}",6.917,11.571,23.714,0.333107 +"{0,3,4,5,6,8,9}",6.917,11.857,24.429,0.314059 +"{0,2,3,6,7,8,9}",6.917,12.286,26.429,0.333787 +"{0,1,2,6,7,9,10}",6.920,11.857,24.429,0.342630 +"{0,1,2,4,8,9,11}",6.923,11.143,22.429,0.348961 +"{0,1,2,5,7,9,11}",6.923,11.429,23.857,0.349509 +"{0,1,2,5,7,8,11}",6.931,12.000,26.143,0.339739 +"{0,1,2,5,6,9,11}",6.933,11.429,23.571,0.342234 +"{0,1,2,4,6,10,11}",6.933,12.143,25.000,0.326890 +"{0,1,2,5,7,9,10}",6.934,11.000,21.857,0.356104 +"{0,2,3,4,5,9,11}",6.936,11.571,24.000,0.338530 +"{0,2,3,5,6,7,10}",6.941,11.143,22.000,0.348167 +"{0,1,2,5,6,8,11}",6.941,11.857,26.000,0.331803 +"{0,1,6,8,9,10,11}",6.941,12.286,23.714,0.309940 +"{0,1,2,5,7,8,10}",6.942,11.143,23.000,0.345673 +"{0,2,3,4,5,8,11}",6.944,11.714,24.286,0.320011 +"{0,4,5,6,9,10,11}",6.946,12.429,26.143,0.334580 +"{0,2,3,4,5,9,10}",6.947,11.571,23.429,0.340514 +"{0,1,2,4,5,10,11}",6.947,12.286,25.000,0.319085 +"{0,1,2,4,6,8,11}",6.955,11.286,23.714,0.343159 +"{0,1,2,3,6,9,11}",6.955,11.857,24.571,0.321070 +"{0,1,3,4,5,9,10}",6.955,11.857,24.429,0.339456 +"{0,1,4,5,6,7,9}",6.955,11.857,24.429,0.329932 +"{0,2,3,4,6,8,9}",6.955,11.857,25.857,0.325718 +"{0,4,5,6,8,10,11}",6.955,12.143,25.714,0.323337 +"{0,2,3,5,6,7,8}",6.963,11.857,24.286,0.315117 +"{0,1,3,4,5,8,10}",6.964,11.000,21.857,0.354516 +"{0,1,2,3,6,9,10}",6.965,11.714,24.286,0.334694 +"{0,1,2,4,6,7,11}",6.968,11.429,23.286,0.348054 +"{0,2,3,4,5,7,10}",6.969,11.286,22.286,0.339701 +"{0,1,2,4,5,8,11}",6.969,11.571,24.143,0.335884 +"{0,1,2,4,7,8,9}",6.969,11.714,24.286,0.348980 +"{0,1,2,3,5,9,11}",6.969,12.143,25.000,0.311017 +"{0,3,5,6,7,10,11}",6.976,11.857,24.000,0.345408 +"{0,1,2,3,5,8,11}",6.977,11.857,24.571,0.314720 +"{0,1,2,4,6,7,10}",6.979,11.857,25.857,0.328893 +"{0,1,2,3,5,9,10}",6.980,11.286,22.571,0.338246 +"{0,3,4,5,9,10,11}",6.982,12.429,26.143,0.337755 +"{0,1,2,3,5,8,10}",6.988,11.143,22.143,0.341289 +"{0,2,3,4,5,7,8}",6.990,11.571,22.429,0.319199 +"{0,3,4,5,8,10,11}",6.990,11.857,24.000,0.340646 +"{0,1,2,3,5,7,11}",6.990,12.143,25.000,0.317366 +"{0,2,3,4,5,6,9}",6.992,12.000,24.714,0.318292 +"{0,1,2,3,5,7,10}",7.001,10.857,21.429,0.346051 +"{0,1,2,3,5,8,9}",7.001,11.857,24.429,0.333107 +"{0,1,3,4,5,6,9}",7.001,11.857,24.429,0.314059 +"{0,1,2,3,6,7,9}",7.001,12.000,26.143,0.333787 +"{0,4,5,6,7,8,10}",7.001,12.429,25.286,0.308239 +"{0,1,2,7,9,10,11}",7.006,12.143,23.571,0.324225 +"{0,1,3,4,5,6,8}",7.009,11.429,22.714,0.319199 +"{0,1,5,6,7,9,11}",7.014,12.000,25.571,0.329686 +"{0,4,5,6,7,8,9}",7.014,12.286,23.571,0.307691 +"{0,6,7,8,9,10,11}",7.014,12.714,24.143,0.287320 +"{0,1,2,3,5,7,9}",7.015,11.429,23.857,0.337207 +"{0,1,2,7,8,10,11}",7.015,12.286,25.000,0.322260 +"{0,2,3,4,9,10,11}",7.020,12.000,24.286,0.334958 +"{0,1,2,3,5,7,8}",7.023,11.571,23.429,0.330990 +"{0,1,2,3,5,6,9}",7.025,11.714,24.286,0.321995 +"{0,1,2,6,8,10,11}",7.025,12.143,25.000,0.322128 +"{0,2,4,5,6,10,11}",7.030,12.143,25.714,0.329686 +"{0,1,2,3,5,6,8}",7.033,11.857,24.286,0.315117 +"{0,1,4,6,7,8,11}",7.036,11.714,23.857,0.353345 +"{0,1,2,6,8,9,11}",7.038,11.571,24.000,0.336943 +"{0,1,2,3,4,6,9}",7.038,11.857,24.571,0.313530 +"{0,1,2,6,7,10,11}",7.038,12.143,24.714,0.335488 +"{0,3,4,5,6,7,11}",7.049,12.143,24.714,0.324376 +"{0,1,4,5,6,9,11}",7.051,11.857,24.000,0.343821 +"{0,2,3,4,8,9,10}",7.052,12.143,25.714,0.330083 +"{0,1,4,5,6,8,11}",7.060,11.857,24.429,0.345408 +"{0,3,4,5,6,7,10}",7.060,12.286,25.143,0.330990 +"{0,5,6,7,8,10,11}",7.060,12.286,24.571,0.311149 +"{0,1,2,5,6,10,11}",7.062,12.286,24.857,0.327551 +"{0,1,2,3,7,10,11}",7.074,12.143,23.429,0.326342 +"{0,1,7,8,9,10,11}",7.074,12.714,24.143,0.300019 +"{0,2,4,5,6,7,10}",7.076,11.857,24.286,0.326096 +"{0,1,3,5,6,7,11}",7.082,12.143,25.714,0.323337 +"{0,3,4,5,6,7,8}",7.082,12.286,23.571,0.301342 +"{0,1,2,3,6,10,11}",7.084,12.143,23.429,0.318405 +"{0,1,4,5,6,7,10}",7.084,12.286,26.429,0.333787 +"{0,2,3,4,7,8,9}",7.087,11.857,24.000,0.345805 +"{0,1,2,3,7,9,11}",7.087,12.143,25.000,0.326890 +"{0,1,2,3,7,8,11}",7.096,12.143,24.714,0.329138 +"{0,2,4,5,6,7,8}",7.097,12.429,25.286,0.305064 +"{0,1,2,3,7,9,10}",7.098,11.714,24.143,0.342101 +"{0,1,2,5,7,8,9}",7.098,11.857,24.000,0.348980 +"{0,2,3,4,5,8,10}",7.098,11.857,24.286,0.321334 +"{0,1,2,3,5,10,11}",7.098,12.286,23.714,0.309940 +"{0,1,2,3,6,8,11}",7.106,12.143,25.000,0.322657 +"{0,1,4,5,6,7,8}",7.106,12.286,24.857,0.321599 +"{0,1,6,7,8,10,11}",7.106,12.286,25.000,0.328609 +"{0,1,2,3,7,8,10}",7.107,11.571,23.429,0.343689 +"{0,2,3,4,5,6,11}",7.111,12.286,25.000,0.306387 +"{0,4,5,6,7,10,11}",7.111,12.429,26.143,0.336168 +"{0,2,3,4,5,8,9}",7.112,11.857,24.000,0.329932 +"{0,1,2,3,6,8,10}",7.117,11.714,24.143,0.335620 +"{0,1,6,7,8,9,11}",7.119,12.143,24.429,0.320673 +"{0,1,2,3,6,7,11}",7.119,12.286,24.857,0.325964 +"{0,1,2,5,6,7,9}",7.122,11.857,24.429,0.345805 +"{0,1,2,3,6,7,10}",7.130,11.714,23.857,0.341043 +"{0,1,3,4,5,6,10}",7.130,11.857,24.286,0.329403 +"{0,1,2,3,6,8,9}",7.130,12.286,26.429,0.329025 +"{0,1,2,8,9,10,11}",7.136,12.714,24.143,0.301606 +"{0,1,2,4,6,7,8}",7.143,12.143,25.714,0.325321 +"{0,1,2,3,5,6,11}",7.143,12.429,25.143,0.301625 +"{0,1,5,6,7,10,11}",7.143,12.429,26.143,0.337755 +"{0,2,3,4,5,6,8}",7.143,12.429,25.286,0.289191 +"{0,1,2,3,4,7,11}",7.147,12.000,23.286,0.321580 +"{0,1,2,3,5,6,10}",7.154,11.571,22.429,0.328723 +"{0,1,2,3,4,6,11}",7.157,12.143,23.571,0.305178 +"{0,3,4,5,6,10,11}",7.157,12.571,26.286,0.331406 +"{0,1,2,3,4,7,10}",7.158,12.000,24.714,0.324641 +"{0,1,3,4,5,6,7}",7.165,12.429,25.143,0.302022 +"{0,1,5,6,7,8,11}",7.165,12.429,26.143,0.331406 +"{0,1,2,3,4,6,10}",7.168,12.286,25.143,0.306652 +"{0,1,2,3,4,7,9}",7.171,12.143,25.000,0.329403 +"{0,1,2,7,8,9,11}",7.171,12.286,25.000,0.334958 +"{0,1,2,3,4,7,8}",7.179,12.286,24.857,0.321599 +"{0,1,4,5,6,10,11}",7.181,12.571,26.286,0.337755 +"{0,1,2,7,8,9,10}",7.182,12.286,24.571,0.327419 +"{0,1,2,3,4,6,8}",7.189,12.286,25.143,0.305064 +"{0,1,2,3,9,10,11}",7.195,12.714,24.143,0.301606 +"{0,1,2,3,4,6,7}",7.203,12.429,25.143,0.302022 +"{0,1,2,6,7,8,11}",7.203,12.429,26.143,0.337755 +"{0,1,2,3,8,10,11}",7.204,12.286,23.714,0.313114 +"{0,2,3,4,5,10,11}",7.208,12.429,25.143,0.320673 +"{0,1,2,6,7,8,10}",7.214,12.286,25.857,0.330083 +"{0,1,4,5,6,7,11}",7.216,12.429,26.143,0.336168 +"{0,1,3,4,5,10,11}",7.217,12.143,24.429,0.328609 +"{0,1,2,3,4,5,7}",7.217,12.286,23.714,0.297638 +"{0,1,2,3,8,9,11}",7.217,12.429,25.143,0.314324 +"{0,1,2,6,7,8,9}",7.227,12.571,26.286,0.334977 +"{0,1,2,3,8,9,10}",7.228,12.429,25.143,0.327419 +"{0,1,2,5,6,7,11}",7.240,12.429,26.143,0.334580 +"{0,1,2,5,6,7,10}",7.251,12.000,24.143,0.341043 +"{0,1,3,4,5,6,11}",7.262,12.429,25.143,0.311149 +"{0,1,2,3,7,8,9}",7.263,12.571,26.286,0.334977 +"{0,2,3,4,5,6,10}",7.265,12.429,25.286,0.308239 +"{0,1,2,3,4,9,11}",7.268,12.143,23.571,0.314701 +"{0,1,2,5,6,7,8}",7.273,12.571,26.286,0.325454 +"{0,1,2,3,4,8,11}",7.276,12.143,23.429,0.316818 +"{0,1,2,4,5,6,11}",7.278,12.143,24.429,0.322260 +"{0,1,2,3,4,9,10}",7.279,12.429,25.143,0.316308 +"{0,1,2,3,4,8,10}",7.287,12.286,25.143,0.317763 +"{0,1,2,3,6,7,8}",7.295,12.571,26.286,0.325454 +"{0,1,2,3,4,8,9}",7.300,12.286,24.857,0.323186 +"{0,2,3,4,5,6,7}",7.300,12.429,23.857,0.297638 +"{0,1,2,4,5,6,7}",7.324,12.429,25.143,0.309958 +"{0,1,2,3,5,6,7}",7.332,12.286,24.571,0.309958 +"{0,1,2,3,4,5,9}",7.338,12.286,23.571,0.307691 +"{0,1,2,3,4,5,8}",7.346,12.286,23.571,0.301342 +"{0,1,2,3,4,10,11}",7.397,12.714,24.143,0.300019 +"{0,1,2,3,4,5,11}",7.457,12.714,24.143,0.287320 +"{0,1,2,3,4,5,10}",7.468,12.429,23.857,0.302400 +"{0,1,2,3,4,5,6}",7.513,12.857,24.286,0.271844 +"{0,3,4,6,7,8,10,11}",6.300,11.500,23.250,0.340505 +"{0,2,4,6,7,8,10,11}",6.314,11.750,24.875,0.332185 +"{0,1,4,5,7,8,9,11}",6.345,11.500,23.250,0.345266 +"{0,2,3,4,7,8,10,11}",6.345,11.500,23.250,0.341695 +"{0,1,3,5,7,8,9,11}",6.353,11.750,24.875,0.328614 +"{0,2,3,4,6,8,10,11}",6.354,11.625,24.750,0.328614 +"{0,2,4,5,6,8,9,10}",6.356,11.875,25.000,0.324150 +"{0,2,3,4,6,7,8,11}",6.385,11.500,23.250,0.336933 +"{0,1,2,5,6,8,9,10}",6.385,11.625,23.375,0.338421 +"{0,1,3,4,5,7,9,11}",6.397,11.625,24.750,0.332185 +"{0,1,2,4,6,8,9,10}",6.397,11.750,24.875,0.331293 +"{0,1,3,4,5,7,8,9}",6.426,11.625,23.375,0.334850 +"{0,2,4,5,7,8,9,11}",6.432,11.250,23.625,0.355882 +"{0,1,3,5,6,8,9,10}",6.466,11.375,23.750,0.346655 +"{0,1,3,4,5,8,9,11}",6.511,11.625,23.375,0.335743 +"{0,2,3,6,7,8,10,11}",6.519,11.625,23.500,0.335743 +"{0,2,3,4,6,7,8,10}",6.519,11.875,25.000,0.325340 +"{0,3,4,5,7,8,9,11}",6.522,11.500,23.375,0.336933 +"{0,2,3,5,8,9,10,11}",6.534,12.250,26.000,0.328713 +"{0,2,4,5,7,9,10,11}",6.538,11.250,23.000,0.357866 +"{0,2,3,5,7,9,10,11}",6.546,11.500,23.625,0.350723 +"{0,2,3,5,7,8,10,11}",6.553,11.500,23.875,0.340405 +"{0,1,2,4,5,6,9,10}",6.555,11.750,23.500,0.336040 +"{0,1,4,5,6,8,9,10}",6.562,11.625,23.500,0.336040 +"{0,1,2,4,5,6,8,10}",6.562,11.875,25.000,0.324150 +"{0,1,3,4,7,8,9,11}",6.563,11.375,23.250,0.341695 +"{0,2,3,4,6,7,10,11}",6.564,11.375,23.250,0.345266 +"{0,2,3,5,7,8,9,11}",6.565,11.750,25.375,0.339725 +"{0,1,3,4,6,9,10,11}",6.574,12.125,25.875,0.334666 +"{0,2,3,5,6,8,9,11}",6.574,12.250,27.250,0.328727 +"{0,2,3,5,6,7,9,10}",6.595,11.375,23.750,0.351417 +"{0,2,3,4,6,7,9,11}",6.597,11.125,23.500,0.355882 +"{0,1,3,4,6,8,9,10}",6.602,11.750,25.375,0.340023 +"{0,1,3,4,5,7,8,11}",6.603,11.375,23.250,0.340505 +"{0,1,2,4,5,8,9,10}",6.607,11.500,23.375,0.338421 +"{0,1,3,4,6,7,9,10}",6.614,12.250,27.250,0.338549 +"{0,2,5,7,8,9,10,11}",6.619,12.125,24.625,0.328997 +"{0,3,4,6,8,9,10,11}",6.625,12.125,25.750,0.329904 +"{0,2,5,6,8,9,10,11}",6.628,12.250,25.875,0.322761 +"{0,3,4,6,7,9,10,11}",6.637,11.875,25.500,0.343396 +"{0,2,4,6,7,9,10,11}",6.651,11.250,23.375,0.354294 +"{0,1,3,6,8,9,10,11}",6.654,12.125,24.625,0.327806 +"{0,2,3,6,7,9,10,11}",6.658,11.625,23.875,0.347647 +"{0,2,5,6,7,8,9,11}",6.658,12.250,26.000,0.333475 +"{0,1,3,6,7,9,10,11}",6.666,12.250,25.875,0.331094 +"{0,2,4,6,7,8,9,11}",6.670,11.500,23.625,0.351913 +"{0,1,4,5,7,9,10,11}",6.671,12.000,25.625,0.340618 +"{0,2,4,5,7,8,10,11}",6.671,12.000,25.625,0.337344 +"{0,2,3,5,6,9,10,11}",6.680,12.125,25.750,0.338634 +"{0,2,3,5,6,8,10,11}",6.687,11.875,25.500,0.327820 +"{0,2,4,5,6,8,9,11}",6.691,11.750,25.375,0.343296 +"{0,1,3,6,7,8,9,10}",6.694,12.375,26.125,0.336154 +"{0,2,3,5,7,8,9,10}",6.699,11.500,23.250,0.347449 +"{0,2,4,5,6,7,9,11}",6.703,11.250,23.000,0.359056 +"{0,1,2,4,7,9,10,11}",6.704,11.875,24.375,0.342092 +"{0,1,3,4,6,8,10,11}",6.706,11.500,23.625,0.348342 +"{0,1,3,5,7,8,9,10}",6.707,11.625,23.750,0.348639 +"{0,2,3,5,6,8,9,10}",6.708,12.000,25.625,0.335261 +"{0,2,3,5,6,7,9,11}",6.710,11.875,25.500,0.343296 +"{0,1,3,4,6,8,9,11}",6.718,11.375,23.750,0.340405 +"{0,1,3,5,6,7,9,10}",6.727,11.875,25.500,0.343594 +"{0,1,3,4,6,7,9,11}",6.729,11.625,25.250,0.337344 +"{0,2,3,4,6,7,9,10}",6.731,11.750,25.375,0.344785 +"{0,3,5,6,8,9,10,11}",6.739,12.500,26.250,0.321570 +"{0,1,2,4,5,7,10,11}",6.744,12.125,25.875,0.338237 +"{0,1,2,4,5,7,9,11}",6.756,11.375,23.500,0.354294 +"{0,2,4,7,8,9,10,11}",6.756,12.250,24.750,0.337330 +"{0,1,3,4,5,7,8,10}",6.759,11.375,23.750,0.351417 +"{0,2,3,7,8,9,10,11}",6.763,12.375,24.750,0.330286 +"{0,2,5,6,7,9,10,11}",6.764,11.875,24.250,0.344870 +"{0,2,4,6,8,9,10,11}",6.764,12.250,26.000,0.330598 +"{0,1,2,4,5,7,9,10}",6.765,11.375,23.750,0.346655 +"{0,1,2,4,5,6,8,9}",6.772,11.625,23.500,0.334850 +"{0,2,3,6,8,9,10,11}",6.772,12.375,26.000,0.325142 +"{0,2,4,5,8,9,10,11}",6.777,12.375,26.000,0.334666 +"{0,3,5,6,7,8,9,10}",6.779,12.250,24.750,0.325723 +"{0,3,4,6,7,8,9,11}",6.781,11.625,23.875,0.334552 +"{0,1,4,6,7,9,10,11}",6.783,12.250,26.000,0.338237 +"{0,1,3,5,8,9,10,11}",6.791,12.250,24.750,0.330187 +"{0,3,4,6,7,8,9,10}",6.791,12.375,26.000,0.325439 +"{0,2,3,6,7,8,9,11}",6.802,12.250,25.875,0.336253 +"{0,1,4,5,7,8,10,11}",6.803,12.000,25.625,0.345777 +"{0,1,3,4,8,9,10,11}",6.803,12.250,24.625,0.332667 +"{0,1,3,5,7,9,10,11}",6.803,12.250,26.000,0.332979 +"{0,1,3,5,7,8,10,11}",6.810,11.375,23.500,0.348342 +"{0,1,3,5,6,9,10,11}",6.812,12.375,26.000,0.331094 +"{0,1,3,4,7,9,10,11}",6.815,12.250,25.875,0.337046 +"{0,2,3,4,6,9,10,11}",6.816,12.000,25.625,0.340618 +"{0,2,4,5,7,8,9,10}",6.817,11.750,23.875,0.337925 +"{0,1,3,5,6,8,10,11}",6.819,11.500,23.250,0.345961 +"{0,1,3,4,7,8,10,11}",6.822,11.625,23.875,0.351219 +"{0,1,4,5,7,8,9,10}",6.824,11.750,24.000,0.339612 +"{0,3,4,5,6,7,9,10}",6.824,12.375,26.125,0.332582 +"{0,1,3,5,6,8,9,11}",6.831,12.125,25.750,0.327820 +"{0,2,3,4,6,8,9,11}",6.835,11.875,25.500,0.339725 +"{0,2,4,5,6,7,8,11}",6.835,12.250,25.875,0.331094 +"{0,1,2,4,7,8,10,11}",6.836,12.250,25.875,0.337046 +"{0,2,4,5,6,7,9,10}",6.837,11.750,23.875,0.341497 +"{0,1,2,4,6,9,10,11}",6.837,12.125,24.625,0.338520 +"{0,1,3,4,6,7,10,11}",6.843,12.125,25.750,0.345777 +"{0,1,3,4,7,8,9,10}",6.843,12.250,25.875,0.342503 +"{0,2,3,5,6,7,8,11}",6.843,12.250,25.875,0.325539 +"{0,3,6,7,8,9,10,11}",6.843,12.500,24.875,0.317191 +"{0,4,5,7,8,9,10,11}",6.848,12.375,24.750,0.326715 +"{0,1,2,4,5,9,10,11}",6.850,12.250,24.625,0.335048 +"{0,3,5,7,8,9,10,11}",6.855,12.375,24.875,0.326616 +"{0,2,6,7,8,9,10,11}",6.856,12.625,25.125,0.317985 +"{0,2,3,4,5,7,9,11}",6.859,11.375,23.500,0.351913 +"{0,1,3,5,6,7,8,10}",6.860,11.500,23.250,0.351020 +"{0,2,3,4,5,7,8,11}",6.867,11.750,24.000,0.334552 +"{0,3,4,7,8,9,10,11}",6.867,12.250,24.500,0.335445 +"{0,1,2,4,6,7,9,11}",6.868,11.250,23.000,0.357866 +"{0,2,3,4,5,7,9,10}",6.869,11.500,23.250,0.351020 +"{0,1,3,4,6,7,8,10}",6.871,12.000,25.625,0.344785 +"{0,1,3,5,6,7,8,9}",6.871,12.375,26.000,0.325439 +"{0,3,5,6,7,9,10,11}",6.875,12.375,26.000,0.333475 +"{0,1,3,4,5,7,9,10}",6.876,12.000,25.625,0.343594 +"{0,1,2,4,6,7,9,10}",6.878,12.000,25.625,0.340023 +"{0,1,3,4,6,7,8,9}",6.883,12.125,25.750,0.329408 +"{0,1,5,6,8,9,10,11}",6.885,12.375,24.750,0.324334 +"{0,4,5,6,7,8,9,11}",6.887,12.375,24.750,0.324334 +"{0,1,2,4,5,7,8,11}",6.888,12.125,25.750,0.343396 +"{0,1,4,7,8,9,10,11}",6.888,12.375,24.750,0.330286 +"{0,3,5,6,7,8,9,11}",6.894,12.500,26.125,0.315618 +"{0,1,3,7,8,9,10,11}",6.895,12.625,25.125,0.322747 +"{0,1,2,4,5,7,8,10}",6.897,11.875,25.500,0.335261 +"{0,1,4,6,8,9,10,11}",6.897,12.250,24.750,0.333759 +"{0,2,5,6,7,8,10,11}",6.897,12.375,26.000,0.323951 +"{0,3,4,5,7,9,10,11}",6.900,12.125,25.375,0.345380 +"{0,3,4,5,7,8,10,11}",6.907,12.000,24.250,0.344076 +"{0,1,4,5,8,9,10,11}",6.909,12.250,24.500,0.336635 +"{0,2,5,6,7,8,9,10}",6.918,12.500,25.000,0.324532 +"{0,2,4,5,6,9,10,11}",6.922,12.250,25.500,0.344189 +"{0,1,3,6,7,8,10,11}",6.923,12.000,24.375,0.344870 +"{0,1,5,6,7,8,9,10}",6.925,12.500,24.875,0.325822 +"{0,3,4,5,6,8,9,11}",6.927,12.375,26.000,0.325539 +"{0,3,4,5,7,8,9,10}",6.928,12.125,24.500,0.338024 +"{0,1,2,6,7,9,10,11}",6.930,12.250,24.625,0.335048 +"{0,1,2,5,8,9,10,11}",6.930,12.375,24.750,0.325524 +"{0,2,4,5,6,8,10,11}",6.930,12.375,27.125,0.323172 +"{0,2,4,6,7,8,9,10}",6.930,12.500,26.250,0.323753 +"{0,2,3,4,7,9,10,11}",6.932,11.750,23.750,0.353203 +"{0,1,3,6,7,8,9,11}",6.935,12.250,25.875,0.326332 +"{0,3,4,5,6,8,9,10}",6.937,12.375,26.000,0.325439 +"{0,1,4,6,7,8,9,10}",6.937,12.500,26.125,0.326630 +"{0,2,3,6,7,8,9,10}",6.937,12.500,26.125,0.333773 +"{0,3,4,5,6,7,9,11}",6.939,12.250,25.875,0.331094 +"{0,1,2,5,7,9,10,11}",6.942,12.125,24.625,0.338520 +"{0,1,2,4,8,9,10,11}",6.942,12.500,25.000,0.327509 +"{0,2,3,5,6,7,10,11}",6.949,12.000,24.250,0.342885 +"{0,1,2,5,7,8,10,11}",6.949,12.375,26.125,0.334666 +"{0,2,3,4,7,8,9,11}",6.951,11.875,24.125,0.351219 +"{0,1,2,5,6,9,10,11}",6.951,12.250,24.500,0.335445 +"{0,1,2,5,6,8,10,11}",6.958,12.250,25.875,0.331094 +"{0,4,6,7,8,9,10,11}",6.960,12.625,25.125,0.316794 +"{0,1,3,5,6,7,9,11}",6.968,12.375,27.125,0.323172 +"{0,1,2,5,6,8,9,11}",6.970,12.125,25.750,0.338634 +"{0,1,2,4,6,8,10,11}",6.970,12.250,26.000,0.332979 +"{0,1,2,3,6,9,10,11}",6.970,12.375,24.750,0.325524 +"{0,1,4,5,6,7,9,10}",6.970,12.375,26.000,0.334170 +"{0,2,3,4,6,8,9,10}",6.970,12.375,27.125,0.327041 +"{0,1,2,4,7,8,9,11}",6.973,11.875,24.250,0.353203 +"{0,2,3,4,5,7,10,11}",6.973,12.000,24.375,0.342489 +"{0,2,3,4,5,8,9,11}",6.973,12.250,25.875,0.336253 +"{0,2,3,5,6,7,8,10}",6.977,11.750,23.875,0.333163 +"{0,1,3,4,5,7,10,11}",6.980,12.125,25.750,0.341808 +"{0,1,2,4,6,8,9,11}",6.981,11.375,23.500,0.350723 +"{0,1,2,4,6,7,10,11}",6.981,12.250,25.875,0.340618 +"{0,4,5,6,8,9,10,11}",6.981,12.750,26.375,0.320082 +"{0,1,2,4,7,8,9,10}",6.982,12.250,25.875,0.334963 +"{0,1,2,4,5,8,10,11}",6.982,12.375,26.000,0.331094 +"{0,1,2,3,5,9,10,11}",6.982,12.500,25.000,0.321556 +"{0,1,3,4,6,7,8,11}",6.987,11.875,24.125,0.344076 +"{0,2,3,5,6,7,8,9}",6.989,12.625,26.375,0.326630 +"{0,1,3,4,5,8,9,10}",6.990,12.000,24.250,0.345564 +"{0,1,2,3,5,8,10,11}",6.990,12.125,24.625,0.327806 +"{0,2,3,4,5,6,9,11}",6.993,12.375,26.125,0.333475 +"{0,4,5,6,7,9,10,11}",6.993,12.500,25.875,0.333177 +"{0,1,2,4,5,8,9,11}",6.994,11.750,24.000,0.347647 +"{0,1,2,3,6,7,9,11}",7.000,12.250,25.875,0.334666 +"{0,2,3,4,5,6,8,11}",7.000,12.375,26.000,0.315618 +"{0,2,3,4,5,7,8,10}",7.001,11.875,24.000,0.333163 +"{0,1,2,3,5,7,10,11}",7.001,12.250,24.750,0.333759 +"{0,1,2,3,5,8,9,11}",7.001,12.375,26.000,0.325142 +"{0,1,5,7,8,9,10,11}",7.001,12.625,25.125,0.320366 +"{0,2,3,4,5,6,9,10}",7.003,12.375,26.000,0.331392 +"{0,3,5,6,7,8,10,11}",7.008,12.125,24.125,0.331774 +"{0,1,2,3,6,7,9,10}",7.010,12.250,25.875,0.342503 +"{0,1,3,4,5,6,9,10}",7.010,12.375,26.000,0.334170 +"{0,1,2,3,5,8,9,10}",7.011,12.000,24.375,0.341596 +"{0,1,2,3,5,7,9,11}",7.013,12.250,26.000,0.330598 +"{0,3,4,5,8,9,10,11}",7.013,12.750,26.375,0.332384 +"{0,1,3,4,5,6,8,10}",7.017,11.625,23.750,0.341497 +"{0,1,2,3,5,7,8,11}",7.020,12.250,25.875,0.329904 +"{0,1,2,3,5,7,9,10}",7.022,11.500,23.625,0.348639 +"{0,1,2,3,5,6,9,11}",7.022,12.375,26.000,0.322761 +"{0,1,5,6,7,9,10,11}",7.022,12.625,26.250,0.327225 +"{0,4,5,6,7,8,9,10}",7.022,12.750,25.250,0.308759 +"{0,1,3,4,5,6,8,9}",7.029,12.000,24.250,0.326516 +"{0,1,2,3,5,6,8,11}",7.029,12.375,26.125,0.321570 +"{0,1,2,3,5,7,8,10}",7.030,11.500,23.250,0.347449 +"{0,1,2,3,5,6,9,10}",7.031,11.875,24.125,0.339612 +"{0,1,2,3,4,6,9,11}",7.033,12.000,24.500,0.328997 +"{0,2,3,4,5,6,7,9}",7.033,12.250,24.750,0.326913 +"{0,3,4,5,6,9,10,11}",7.033,13.000,28.000,0.332894 +"{0,1,2,4,5,7,8,9}",7.034,12.000,24.250,0.344374 +"{0,1,2,3,5,6,8,10}",7.039,11.750,23.875,0.337925 +"{0,1,4,6,7,8,10,11}",7.041,12.375,26.000,0.341808 +"{0,3,4,5,6,8,10,11}",7.041,12.375,25.625,0.331094 +"{0,1,3,4,5,6,7,9}",7.041,12.500,26.125,0.317106 +"{0,1,2,3,4,6,9,10}",7.043,12.375,26.000,0.326630 +"{0,1,2,6,8,9,10,11}",7.043,12.625,25.125,0.321556 +"{0,2,3,4,8,9,10,11}",7.046,12.750,26.375,0.329606 +"{0,1,2,3,5,6,8,9}",7.050,12.375,26.000,0.329408 +"{0,1,4,6,7,8,9,11}",7.052,11.875,23.875,0.342489 +"{0,1,4,5,6,9,10,11}",7.055,12.750,26.375,0.337146 +"{0,1,4,5,6,8,10,11}",7.062,12.250,25.500,0.341808 +"{0,2,4,5,6,7,10,11}",7.066,12.250,25.500,0.339427 +"{0,3,4,5,6,7,8,11}",7.071,12.375,24.625,0.322350 +"{0,1,4,5,6,8,9,11}",7.074,12.125,24.375,0.342885 +"{0,1,2,3,4,6,7,9}",7.074,12.375,26.125,0.326630 +"{0,5,6,7,8,9,10,11}",7.074,13.000,25.500,0.301020 +"{0,2,3,4,5,9,10,11}",7.079,12.625,26.000,0.336749 +"{0,1,3,5,6,7,10,11}",7.081,12.375,25.625,0.341808 +"{0,3,4,5,6,7,8,10}",7.081,12.500,25.000,0.323342 +"{0,1,4,5,6,7,9,11}",7.085,12.250,25.500,0.339427 +"{0,1,2,5,7,8,9,11}",7.086,12.375,26.000,0.340618 +"{0,2,3,4,7,8,9,10}",7.086,12.375,25.625,0.339725 +"{0,2,3,4,5,8,10,11}",7.086,12.500,26.125,0.326332 +"{0,1,2,3,7,9,10,11}",7.086,12.625,25.125,0.327509 +"{0,1,3,4,5,9,10,11}",7.086,12.750,26.375,0.329606 +"{0,1,3,4,5,8,10,11}",7.093,11.875,23.875,0.344870 +"{0,1,2,3,7,8,10,11}",7.093,12.375,24.750,0.332667 +"{0,3,4,5,6,7,8,9}",7.093,12.625,25.000,0.310346 +"{0,2,4,5,6,7,8,10}",7.095,12.625,26.375,0.313039 +"{0,1,2,5,7,8,9,10}",7.096,12.000,24.000,0.341596 +"{0,1,3,5,6,7,8,11}",7.100,12.250,25.500,0.331094 +"{0,1,2,3,6,8,10,11}",7.102,12.250,24.750,0.330187 +"{0,1,4,5,6,7,8,10}",7.102,12.500,26.125,0.331392 +"{0,1,2,5,6,7,9,11}",7.106,12.125,25.375,0.344189 +"{0,2,4,5,6,7,8,9}",7.106,12.500,25.000,0.325723 +"{0,2,3,4,5,8,9,10}",7.107,12.375,25.625,0.333773 +"{0,1,2,3,6,7,10,11}",7.114,12.375,24.625,0.336635 +"{0,1,2,3,6,8,9,11}",7.114,12.500,26.250,0.328713 +"{0,1,4,5,6,7,8,9}",7.114,12.500,24.750,0.325028 +"{0,1,6,7,8,9,10,11}",7.114,13.000,25.500,0.310544 +"{0,1,2,5,6,7,9,10}",7.116,12.125,24.375,0.345564 +"{0,1,2,3,6,8,9,10}",7.123,12.500,26.125,0.334963 +"{0,1,2,4,6,7,8,11}",7.125,12.250,25.500,0.345380 +"{0,1,3,4,5,6,9,11}",7.125,12.500,26.125,0.323951 +"{0,2,3,4,6,7,8,9}",7.125,12.500,26.125,0.329011 +"{0,4,5,6,7,8,10,11}",7.125,12.875,26.500,0.321273 +"{0,1,3,4,5,6,8,11}",7.133,12.125,24.500,0.331774 +"{0,1,2,3,5,6,10,11}",7.135,12.500,24.875,0.324334 +"{0,1,2,4,6,7,8,10}",7.135,12.500,27.250,0.327041 +"{0,2,3,4,5,6,8,10}",7.135,12.500,26.250,0.313039 +"{0,2,3,4,5,6,7,11}",7.137,12.375,24.750,0.324334 +"{0,2,3,4,5,7,8,9}",7.138,12.125,24.125,0.335643 +"{0,1,2,3,4,7,10,11}",7.138,12.375,24.750,0.330286 +"{0,1,2,4,5,6,9,11}",7.139,11.875,23.875,0.344870 +"{0,1,2,4,5,6,8,11}",7.147,12.250,25.875,0.333475 +"{0,1,2,4,6,7,8,9}",7.147,12.250,25.500,0.337344 +"{0,1,2,3,4,6,10,11}",7.147,12.500,25.000,0.320366 +"{0,2,3,4,5,6,8,9}",7.147,12.625,26.250,0.317106 +"{0,1,2,3,4,7,9,11}",7.150,12.125,24.625,0.337330 +"{0,1,3,4,5,6,7,10}",7.154,12.625,26.375,0.332582 +"{0,1,5,6,7,8,10,11}",7.154,12.625,26.000,0.331987 +"{0,1,2,3,4,7,8,11}",7.157,12.250,24.500,0.335445 +"{0,1,2,3,4,7,9,10}",7.159,12.500,26.250,0.336154 +"{0,1,2,7,8,9,10,11}",7.159,13.000,25.500,0.317687 +"{0,1,2,3,4,6,8,11}",7.166,12.250,24.750,0.326616 +"{0,1,2,3,4,7,8,10}",7.166,12.375,26.000,0.333773 +"{0,1,2,3,5,7,8,9}",7.166,12.375,25.625,0.337344 +"{0,1,5,6,7,8,9,11}",7.166,12.875,26.500,0.322463 +"{0,1,3,4,5,6,7,8}",7.173,12.500,24.875,0.317489 +"{0,1,2,3,4,6,8,10}",7.175,12.375,26.125,0.323753 +"{0,1,2,3,4,6,7,11}",7.177,12.375,24.750,0.326715 +"{0,3,4,5,6,7,10,11}",7.177,12.750,26.375,0.337146 +"{0,1,2,4,5,6,7,9}",7.180,12.125,24.500,0.335643 +"{0,1,2,3,4,6,8,9}",7.187,12.375,26.000,0.325439 +"{0,1,2,3,5,6,7,9}",7.187,12.375,26.000,0.329011 +"{0,1,2,3,4,6,7,10}",7.187,12.500,26.125,0.325439 +"{0,1,2,6,7,8,10,11}",7.187,12.750,26.375,0.329606 +"{0,1,2,3,4,5,7,11}",7.190,12.500,25.000,0.316794 +"{0,1,2,3,4,5,7,10}",7.199,12.250,24.750,0.325723 +"{0,1,2,6,7,8,9,11}",7.199,12.625,26.000,0.336749 +"{0,1,2,3,8,9,10,11}",7.199,13.000,25.500,0.314116 +"{0,1,4,5,6,7,10,11}",7.199,13.000,28.000,0.338846 +"{0,1,2,6,7,8,9,10}",7.208,13.000,26.625,0.327523 +"{0,1,2,3,4,5,7,9}",7.211,12.375,24.875,0.325723 +"{0,1,2,3,4,5,7,8}",7.218,12.500,24.875,0.317489 +"{0,1,4,5,6,7,8,11}",7.218,12.750,26.375,0.337146 +"{0,1,2,5,6,7,10,11}",7.220,12.750,26.375,0.337146 +"{0,1,2,3,7,8,9,11}",7.230,12.750,26.375,0.329606 +"{0,2,3,4,5,6,10,11}",7.231,12.750,26.375,0.322463 +"{0,1,3,4,5,6,10,11}",7.239,12.750,26.125,0.331987 +"{0,1,2,5,6,7,8,11}",7.239,13.000,28.000,0.332894 +"{0,1,2,3,7,8,9,10}",7.240,12.750,26.125,0.337046 +"{0,1,2,3,4,9,10,11}",7.244,12.875,25.375,0.317687 +"{0,1,2,5,6,7,8,10}",7.248,12.500,25.750,0.333773 +"{0,1,2,3,4,8,10,11}",7.251,12.625,25.125,0.322747 +"{0,1,2,4,5,6,10,11}",7.253,12.875,26.500,0.327225 +"{0,1,2,3,6,7,8,11}",7.258,12.750,26.375,0.332384 +"{0,1,2,5,6,7,8,9}",7.260,12.875,26.500,0.333872 +"{0,1,2,3,4,8,9,11}",7.263,12.375,24.750,0.330286 +"{0,1,2,3,6,7,8,10}",7.267,12.375,25.625,0.339725 +"{0,1,3,4,5,6,7,11}",7.269,12.750,26.375,0.321273 +"{0,2,3,4,5,6,7,10}",7.272,12.500,25.000,0.323342 +"{0,1,2,3,4,8,9,10}",7.272,12.750,26.375,0.327523 +"{0,1,2,3,6,7,8,9}",7.279,13.125,28.125,0.330811 +"{0,1,2,4,5,6,7,11}",7.283,12.625,26.000,0.333177 +"{0,1,2,3,5,6,7,11}",7.291,12.875,26.500,0.320082 +"{0,2,3,4,5,6,7,8}",7.291,12.875,25.375,0.301616 +"{0,1,2,4,5,6,7,10}",7.293,12.625,26.250,0.325439 +"{0,1,2,3,4,5,9,11}",7.296,12.625,25.125,0.317985 +"{0,1,2,3,5,6,7,10}",7.300,12.125,24.125,0.338024 +"{0,1,2,3,4,5,8,11}",7.303,12.500,24.875,0.317191 +"{0,1,2,3,4,7,8,9}",7.303,12.750,26.375,0.333872 +"{0,1,2,3,4,5,9,10}",7.305,12.500,24.875,0.325822 +"{0,1,2,4,5,6,7,8}",7.312,12.875,26.500,0.316808 +"{0,1,2,3,4,5,8,10}",7.313,12.375,24.875,0.324532 +"{0,1,2,3,5,6,7,8}",7.319,12.750,26.125,0.320380 +"{0,1,2,3,4,5,8,9}",7.324,12.500,24.750,0.325028 +"{0,1,2,3,4,6,7,8}",7.331,12.875,26.500,0.316808 +"{0,1,2,3,4,5,6,9}",7.345,12.625,25.000,0.310346 +"{0,1,2,3,4,5,6,8}",7.352,12.750,25.250,0.301616 +"{0,1,2,3,4,5,10,11}",7.409,13.000,25.500,0.310544 +"{0,1,2,3,4,5,6,11}",7.449,13.000,25.500,0.301020 +"{0,1,2,3,4,5,6,10}",7.458,12.875,25.375,0.308759 +"{0,1,2,3,4,5,6,7}",7.489,13.125,25.625,0.295366 +"{0,2,3,4,6,7,8,10,11}",6.573,12.000,24.667,0.335681 +"{0,1,3,4,5,7,8,9,11}",6.601,12.000,24.667,0.335681 +"{0,1,2,4,5,6,8,9,10}",6.629,12.111,24.778,0.333135 +"{0,2,3,5,7,8,9,10,11}",6.733,12.444,25.778,0.335373 +"{0,2,3,5,6,8,9,10,11}",6.741,12.667,27.111,0.328671 +"{0,1,3,4,6,8,9,10,11}",6.757,12.333,25.667,0.337224 +"{0,1,3,4,6,7,9,10,11}",6.768,12.556,27.000,0.337930 +"{0,1,2,4,5,7,9,10,11}",6.791,12.222,25.556,0.343706 +"{0,2,4,5,7,8,9,10,11}",6.837,12.556,25.889,0.338150 +"{0,1,3,5,6,8,9,10,11}",6.858,12.556,25.889,0.332595 +"{0,2,3,5,6,7,9,10,11}",6.862,12.333,25.556,0.343089 +"{0,2,3,4,6,7,9,10,11}",6.872,12.111,25.333,0.347718 +"{0,2,4,5,6,7,8,9,11}",6.872,12.444,25.778,0.339076 +"{0,2,3,5,6,7,8,9,11}",6.879,12.778,27.222,0.329597 +"{0,1,2,4,6,7,9,10,11}",6.891,12.333,25.667,0.343706 +"{0,1,3,5,6,7,8,9,10}",6.894,12.556,25.889,0.336530 +"{0,1,3,4,6,7,8,9,10}",6.904,12.778,27.222,0.334458 +"{0,1,2,4,5,7,8,10,11}",6.909,12.667,27.111,0.337930 +"{0,3,4,6,7,8,9,10,11}",6.925,12.667,25.889,0.328968 +"{0,2,5,6,7,8,9,10,11}",6.927,12.889,26.222,0.323104 +"{0,2,4,6,7,8,9,10,11}",6.937,12.667,26.000,0.332286 +"{0,2,3,6,7,8,9,10,11}",6.944,12.778,26.000,0.329894 +"{0,1,3,6,7,8,9,10,11}",6.950,12.889,26.222,0.326808 +"{0,1,4,5,7,8,9,10,11}",6.955,12.667,25.889,0.334524 +"{0,2,4,5,6,8,9,10,11}",6.956,12.778,27.111,0.330214 +"{0,1,3,5,7,8,9,10,11}",6.962,12.667,26.000,0.331360 +"{0,2,4,5,6,7,9,10,11}",6.967,12.333,25.333,0.345558 +"{0,1,3,4,7,8,9,10,11}",6.972,12.667,25.889,0.336376 +"{0,2,3,4,6,8,9,10,11}",6.973,12.667,27.000,0.332066 +"{0,2,3,5,6,7,8,10,11}",6.980,12.444,25.667,0.331978 +"{0,1,3,5,6,7,9,10,11}",6.980,12.778,27.111,0.332066 +"{0,1,2,5,6,8,9,10,11}",6.982,12.667,25.889,0.330820 +"{0,1,2,4,7,8,9,10,11}",6.984,12.778,26.111,0.335141 +"{0,1,2,4,6,8,9,10,11}",6.992,12.556,25.889,0.335064 +"{0,2,3,4,5,7,9,10,11}",6.995,12.333,25.333,0.347410 +"{0,1,3,4,6,7,8,10,11}",6.997,12.333,25.556,0.345866 +"{0,2,3,5,6,7,8,9,10}",6.999,12.667,26.000,0.332826 +"{0,2,3,4,6,7,8,9,11}",7.000,12.333,25.556,0.341237 +"{0,2,3,4,5,7,8,10,11}",7.001,12.444,25.667,0.336607 +"{0,1,3,4,5,7,9,10,11}",7.001,12.667,27.000,0.336695 +"{0,1,2,4,5,8,9,10,11}",7.003,12.667,25.889,0.335450 +"{0,1,3,4,6,7,8,9,11}",7.007,12.222,25.444,0.336607 +"{0,1,3,4,5,7,8,10,11}",7.008,12.222,25.444,0.345866 +"{0,1,2,3,6,7,9,10,11}",7.009,12.667,25.889,0.335450 +"{0,1,2,3,5,8,9,10,11}",7.010,12.778,26.111,0.327734 +"{0,2,3,4,5,7,8,9,11}",7.011,12.333,25.556,0.341237 +"{0,2,3,4,5,6,8,9,11}",7.019,12.778,27.222,0.329597 +"{0,1,2,3,5,7,9,10,11}",7.020,12.556,25.889,0.335064 +"{0,3,5,6,7,8,9,10,11}",7.026,13.000,26.333,0.319400 +"{0,1,2,3,5,7,8,10,11}",7.027,12.444,25.778,0.337224 +"{0,1,3,4,5,7,8,9,10}",7.027,12.444,25.667,0.341468 +"{0,1,2,3,5,6,9,10,11}",7.028,12.667,25.889,0.330820 +"{0,1,2,4,5,7,8,9,11}",7.030,12.333,25.556,0.347718 +"{0,2,3,4,5,6,7,9,11}",7.030,12.444,25.778,0.339076 +"{0,1,2,3,5,6,8,10,11}",7.034,12.444,25.778,0.332595 +"{0,1,3,4,5,6,8,9,10}",7.034,12.444,25.667,0.336839 +"{0,2,3,4,5,6,7,9,10}",7.038,12.556,25.889,0.335604 +"{0,1,2,3,4,6,9,10,11}",7.038,12.667,26.000,0.330511 +"{0,1,2,4,5,7,8,9,10}",7.039,12.333,25.556,0.338690 +"{0,1,2,3,5,6,8,9,11}",7.045,12.778,27.222,0.328671 +"{0,1,3,4,5,6,7,9,10}",7.045,12.889,27.333,0.332606 +"{0,3,4,5,7,8,9,10,11}",7.047,12.778,26.000,0.333598 +"{0,1,2,3,5,6,8,9,10}",7.053,12.444,25.667,0.338690 +"{0,1,4,6,7,8,9,10,11}",7.055,12.889,26.222,0.329586 +"{0,3,4,5,6,8,9,10,11}",7.055,13.111,27.556,0.326587 +"{0,1,2,3,4,6,7,9,11}",7.065,12.333,25.667,0.338150 +"{0,3,4,5,6,7,9,10,11}",7.065,13.000,27.444,0.333995 +"{0,1,2,3,4,6,7,9,10}",7.074,12.778,27.222,0.334458 +"{0,1,4,5,6,8,9,10,11}",7.074,12.778,26.000,0.334524 +"{0,2,3,4,7,8,9,10,11}",7.076,12.778,26.000,0.339153 +"{0,3,4,5,6,7,8,9,11}",7.082,12.778,26.000,0.322487 +"{0,2,4,5,6,7,8,10,11}",7.084,12.889,27.222,0.326510 +"{0,1,4,5,6,7,9,10,11}",7.084,13.000,27.444,0.334921 +"{0,1,2,5,7,8,9,10,11}",7.085,12.889,26.222,0.330511 +"{0,3,4,5,6,7,8,9,10}",7.091,13.000,26.333,0.320558 +"{0,2,3,4,5,8,9,10,11}",7.095,13.111,27.556,0.332143 +"{0,1,3,5,6,7,8,10,11}",7.097,12.444,25.444,0.340928 +"{0,1,3,4,5,8,9,10,11}",7.102,12.778,26.000,0.336376 +"{0,1,2,5,6,7,9,10,11}",7.103,12.667,25.889,0.338228 +"{0,2,4,5,6,7,8,9,10}",7.103,12.889,26.222,0.322332 +"{0,1,3,5,6,7,8,9,11}",7.108,12.889,27.222,0.323732 +"{0,1,2,3,6,8,9,10,11}",7.110,12.889,26.222,0.327734 +"{0,1,4,5,6,7,8,9,10}",7.110,12.889,26.111,0.326422 +"{0,2,3,4,5,6,9,10,11}",7.114,13.000,27.444,0.334921 +"{0,1,2,4,6,7,8,10,11}",7.120,12.778,27.111,0.336695 +"{0,2,3,4,5,6,8,10,11}",7.120,12.778,27.111,0.323732 +"{0,2,3,4,6,7,8,9,10}",7.120,12.889,27.222,0.329519 +"{0,1,3,4,5,6,9,10,11}",7.120,13.111,27.556,0.332143 +"{0,1,3,4,5,6,8,10,11}",7.127,12.444,25.444,0.340928 +"{0,1,2,4,6,7,8,9,11}",7.130,12.333,25.333,0.347410 +"{0,4,5,6,7,8,9,10,11}",7.130,13.222,26.556,0.316391 +"{0,2,3,4,5,7,8,9,10}",7.131,12.556,25.556,0.337456 +"{0,1,2,4,5,6,9,10,11}",7.132,12.778,26.000,0.338228 +"{0,1,3,4,5,6,8,9,11}",7.137,12.556,25.778,0.331978 +"{0,1,2,4,5,6,8,10,11}",7.139,12.778,27.111,0.332066 +"{0,1,2,4,6,7,8,9,10}",7.139,12.889,27.222,0.330445 +"{0,2,3,4,5,6,8,9,10}",7.139,12.889,27.222,0.324890 +"{0,1,2,3,4,7,9,10,11}",7.141,12.778,26.111,0.335141 +"{0,1,3,4,5,6,7,9,11}",7.147,12.778,27.111,0.326510 +"{0,2,3,4,5,6,7,8,11}",7.147,12.778,26.000,0.322487 +"{0,1,2,3,4,7,8,10,11}",7.148,12.667,25.889,0.336376 +"{0,1,2,3,5,7,8,9,11}",7.148,12.778,27.111,0.332066 +"{0,1,2,4,5,6,8,9,11}",7.149,12.333,25.556,0.343089 +"{0,1,2,3,4,6,8,10,11}",7.156,12.556,25.889,0.331360 +"{0,1,5,6,7,8,9,10,11}",7.156,13.222,26.556,0.319169 +"{0,1,2,3,5,7,8,9,10}",7.157,12.444,25.444,0.343011 +"{0,1,2,4,5,6,7,9,11}",7.160,12.333,25.333,0.345558 +"{0,1,3,4,5,6,7,8,10}",7.162,12.667,26.000,0.335604 +"{0,1,2,3,4,6,8,9,11}",7.166,12.444,25.778,0.335373 +"{0,1,2,3,4,6,7,10,11}",7.166,12.667,25.889,0.334524 +"{0,1,2,3,5,6,7,9,11}",7.166,12.778,27.111,0.330214 +"{0,1,2,4,5,6,7,9,10}",7.168,12.556,25.778,0.336839 +"{0,1,3,4,5,6,7,8,9}",7.173,12.889,26.111,0.320866 +"{0,1,2,3,5,6,7,9,10}",7.175,12.444,25.667,0.341468 +"{0,1,2,3,4,6,8,9,10}",7.175,12.778,27.111,0.330445 +"{0,1,2,3,4,5,7,10,11}",7.177,12.778,26.111,0.329586 +"{0,3,4,5,6,7,8,10,11}",7.183,12.889,26.111,0.329894 +"{0,1,2,6,7,8,9,10,11}",7.185,13.222,26.556,0.324724 +"{0,1,2,3,4,5,7,9,11}",7.188,12.556,25.889,0.332286 +"{0,1,2,3,4,5,7,8,11}",7.194,12.667,25.889,0.328968 +"{0,1,2,3,4,5,7,9,10}",7.196,12.556,25.889,0.336530 +"{0,1,4,5,6,7,8,10,11}",7.202,13.111,27.556,0.335847 +"{0,1,2,3,4,5,7,8,10}",7.203,12.556,25.889,0.332826 +"{0,1,4,5,6,7,8,9,11}",7.212,12.889,26.111,0.333598 +"{0,1,2,3,7,8,9,10,11}",7.213,13.222,26.556,0.326576 +"{0,1,2,5,6,7,8,10,11}",7.221,13.111,27.556,0.332143 +"{0,1,2,5,6,7,8,9,11}",7.231,13.111,27.556,0.334921 +"{0,1,2,3,6,7,8,10,11}",7.238,12.778,26.000,0.336376 +"{0,1,2,5,6,7,8,9,10}",7.240,13.000,26.222,0.332903 +"{0,2,3,4,5,6,7,10,11}",7.242,12.778,26.000,0.333598 +"{0,1,2,3,4,8,9,10,11}",7.242,13.111,26.444,0.326576 +"{0,1,2,3,6,7,8,9,11}",7.248,13.111,27.556,0.332143 +"{0,1,3,4,5,6,7,10,11}",7.248,13.111,27.556,0.335847 +"{0,1,2,3,6,7,8,9,10}",7.257,13.222,27.667,0.335152 +"{0,1,2,4,5,6,7,10,11}",7.260,13.111,27.556,0.334921 +"{0,1,3,4,5,6,7,8,11}",7.265,12.778,26.000,0.329894 +"{0,1,2,3,5,6,7,10,11}",7.267,12.889,26.111,0.334524 +"{0,2,3,4,5,6,7,8,10}",7.267,12.889,26.222,0.318629 +"{0,1,2,3,4,7,8,9,11}",7.269,12.667,25.889,0.339153 +"{0,1,2,3,4,5,9,10,11}",7.271,13.111,26.444,0.324724 +"{0,1,2,4,5,6,7,8,11}",7.277,13.111,27.556,0.333995 +"{0,2,3,4,5,6,7,8,9}",7.277,13.111,26.444,0.318706 +"{0,1,2,3,4,5,8,10,11}",7.278,12.889,26.222,0.326808 +"{0,1,2,3,4,7,8,9,10}",7.278,13.111,27.556,0.335152 +"{0,1,2,3,5,6,7,8,11}",7.284,13.111,27.556,0.326587 +"{0,1,2,4,5,6,7,8,10}",7.286,13.000,27.333,0.324890 +"{0,1,2,3,4,5,8,9,11}",7.288,12.778,26.000,0.329894 +"{0,1,2,3,5,6,7,8,10}",7.292,12.556,25.556,0.337456 +"{0,1,2,3,4,6,7,8,11}",7.294,12.778,26.000,0.333598 +"{0,1,2,4,5,6,7,8,9}",7.296,12.889,26.111,0.330126 +"{0,1,2,3,4,5,8,9,10}",7.297,12.778,26.000,0.332903 +"{0,1,2,3,4,6,7,8,10}",7.303,12.889,27.222,0.329519 +"{0,1,2,3,5,6,7,8,9}",7.303,13.222,27.667,0.327745 +"{0,1,2,3,4,5,6,9,11}",7.306,12.889,26.222,0.323104 +"{0,1,2,3,4,5,6,8,11}",7.313,12.889,26.222,0.319400 +"{0,1,2,3,4,6,7,8,9}",7.313,13.111,27.556,0.327745 +"{0,1,2,3,4,5,6,9,10}",7.315,12.889,26.111,0.326422 +"{0,1,2,3,4,5,6,8,10}",7.322,12.778,26.111,0.322332 +"{0,1,2,3,4,5,7,8,9}",7.324,12.889,26.111,0.330126 +"{0,1,2,3,4,5,6,8,9}",7.332,12.889,26.111,0.320866 +"{0,1,2,3,4,5,6,7,9}",7.342,13.000,26.333,0.318706 +"{0,1,2,3,4,5,6,10,11}",7.407,13.222,26.556,0.319169 +"{0,1,2,3,4,5,6,7,11}",7.434,13.222,26.556,0.316391 +"{0,1,2,3,4,5,6,7,10}",7.443,13.111,26.444,0.320558 +"{0,1,2,3,4,5,6,7,8}",7.460,13.333,26.667,0.309215 +"{0,2,3,5,6,7,8,9,10,11}",6.999,13.100,27.100,0.329480 +"{0,1,3,4,6,7,8,9,10,11}",7.014,13.000,27.000,0.334665 +"{0,1,2,4,5,7,8,9,10,11}",7.035,13.000,27.000,0.337628 +"{0,1,2,3,5,6,8,9,10,11}",7.048,13.000,27.000,0.331702 +"{0,1,2,3,4,6,7,9,10,11}",7.067,12.900,26.900,0.337628 +"{0,2,4,5,6,7,8,9,10,11}",7.093,13.200,27.200,0.329233 +"{0,1,3,5,6,7,8,9,10,11}",7.105,13.200,27.200,0.328492 +"{0,2,3,4,6,7,8,9,10,11}",7.108,13.000,26.900,0.334665 +"{0,2,3,4,5,7,8,9,10,11}",7.118,13.100,27.100,0.336887 +"{0,1,3,4,5,7,8,9,10,11}",7.124,13.000,26.900,0.336146 +"{0,1,2,4,6,7,8,9,10,11}",7.125,13.100,27.100,0.335159 +"{0,2,3,4,5,6,8,9,10,11}",7.125,13.300,28.300,0.329056 +"{0,1,3,4,5,6,8,9,10,11}",7.131,13.100,27.100,0.334665 +"{0,2,3,4,5,6,7,9,10,11}",7.134,13.000,27.000,0.339109 +"{0,1,3,4,5,6,7,9,10,11}",7.140,13.300,28.300,0.332760 +"{0,1,2,3,5,7,8,9,10,11}",7.141,13.100,27.100,0.332937 +"{0,1,2,4,5,6,8,9,10,11}",7.142,13.000,26.900,0.335406 +"{0,2,3,4,5,6,7,8,9,11}",7.150,13.100,27.100,0.330220 +"{0,1,2,4,5,6,7,9,10,11}",7.151,13.000,27.000,0.339850 +"{0,1,2,3,4,6,8,9,10,11}",7.157,13.000,27.000,0.332937 +"{0,1,2,3,5,6,7,9,10,11}",7.157,13.000,26.900,0.335406 +"{0,1,3,4,5,6,7,8,9,10}",7.163,13.200,27.200,0.330406 +"{0,1,2,3,4,5,7,9,10,11}",7.176,13.000,27.000,0.335159 +"{0,1,2,3,4,5,7,8,10,11}",7.182,13.000,27.000,0.334665 +"{0,3,4,5,6,7,8,9,10,11}",7.182,13.400,27.400,0.324109 +"{0,1,4,5,6,7,8,9,10,11}",7.199,13.400,27.400,0.328554 +"{0,1,2,5,6,7,8,9,10,11}",7.216,13.400,27.400,0.329295 +"{0,1,2,3,6,7,8,9,10,11}",7.231,13.400,27.400,0.330776 +"{0,2,3,4,5,6,7,8,10,11}",7.240,13.100,27.000,0.327998 +"{0,1,3,4,5,6,7,8,10,11}",7.246,13.100,27.100,0.337628 +"{0,1,2,3,4,7,8,9,10,11}",7.250,13.300,27.300,0.334480 +"{0,1,3,4,5,6,7,8,9,11}",7.255,13.100,27.000,0.327998 +"{0,2,3,4,5,6,7,8,9,10}",7.257,13.300,27.300,0.324974 +"{0,1,2,4,5,6,7,8,10,11}",7.257,13.400,28.400,0.332760 +"{0,1,2,3,5,6,7,8,10,11}",7.263,13.100,27.100,0.334665 +"{0,1,2,4,5,6,7,8,9,11}",7.267,13.100,27.100,0.339109 +"{0,1,2,3,4,5,8,9,10,11}",7.267,13.300,27.300,0.330776 +"{0,1,2,3,4,6,7,8,10,11}",7.272,13.000,26.900,0.336146 +"{0,1,2,3,5,6,7,8,9,11}",7.272,13.400,28.400,0.329056 +"{0,1,2,4,5,6,7,8,9,10}",7.274,13.200,27.100,0.329665 +"{0,1,2,3,5,6,7,8,9,10}",7.280,13.200,27.200,0.335591 +"{0,1,2,3,4,6,7,8,9,11}",7.282,13.000,27.000,0.336887 +"{0,1,2,3,4,5,6,9,10,11}",7.284,13.300,27.300,0.329295 +"{0,1,2,3,4,5,6,8,10,11}",7.289,13.100,27.100,0.328492 +"{0,1,2,3,4,6,7,8,9,10}",7.289,13.400,28.400,0.331464 +"{0,1,2,3,4,5,7,8,9,11}",7.292,13.000,26.900,0.334665 +"{0,1,2,3,4,5,6,8,9,11}",7.299,13.100,27.100,0.329480 +"{0,1,2,3,4,5,7,8,9,10}",7.299,13.100,27.100,0.335591 +"{0,1,2,3,4,5,6,8,9,10}",7.306,13.100,27.000,0.329665 +"{0,1,2,3,4,5,6,7,9,11}",7.308,13.100,27.100,0.329233 +"{0,1,2,3,4,5,6,7,9,10}",7.316,13.200,27.200,0.330406 +"{0,1,2,3,4,5,6,7,10,11}",7.399,13.400,27.400,0.328554 +"{0,1,2,3,4,5,6,7,8,11}",7.414,13.400,27.400,0.324109 +"{0,1,2,3,4,5,6,7,8,10}",7.422,13.300,27.300,0.324974 +"{0,1,2,3,4,5,6,7,8,9}",7.431,13.500,27.500,0.321332 +"{0,2,3,4,5,6,7,8,9,10,11}",7.234,13.545,28.091,0.329733 +"{0,1,3,4,5,6,7,8,9,10,11}",7.239,13.545,28.091,0.330945 +"{0,1,2,4,5,6,7,8,9,10,11}",7.249,13.545,28.091,0.333369 +"{0,1,2,3,5,6,7,8,9,10,11}",7.255,13.545,28.091,0.331551 +"{0,1,2,3,4,6,7,8,9,10,11}",7.263,13.455,28.000,0.334582 +"{0,1,2,3,4,5,7,8,9,10,11}",7.272,13.455,28.000,0.334582 +"{0,1,2,3,4,5,6,8,9,10,11}",7.279,13.455,28.000,0.331551 +"{0,1,2,3,4,5,6,7,9,10,11}",7.287,13.455,28.000,0.333369 +"{0,1,2,3,4,5,6,7,8,10,11}",7.383,13.545,28.091,0.330945 +"{0,1,2,3,4,5,6,7,8,9,11}",7.392,13.545,28.091,0.329733 +"{0,1,2,3,4,5,6,7,8,9,10}",7.399,13.636,28.182,0.328672 +"{0,1,2,3,4,5,6,7,8,9,10,11}",7.366,13.833,28.833,0.331475 \ No newline at end of file diff --git a/inst/stolz15/readme.txt b/inst/stolz15/readme.txt new file mode 100644 index 0000000..8560637 --- /dev/null +++ b/inst/stolz15/readme.txt @@ -0,0 +1,5 @@ +Tables of computed harmoniousness values, in particular periodicity, for all 2048 possible harmonies consisting of up to 12 semitones + +http://ai-linux.hs-harz.de/fstolzenburg/harmony/ (sourced 10 April 2018) + +Manually converted to csv format. diff --git a/man/bowl18_min_freq_dist.Rd b/man/bowl18_min_freq_dist.Rd new file mode 100644 index 0000000..78d23d8 --- /dev/null +++ b/man/bowl18_min_freq_dist.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-bowl18.R +\name{bowl18_min_freq_dist} +\alias{bowl18_min_freq_dist} +\alias{bowl18_min_freq_dist.default} +\alias{bowl18_min_freq_dist.fr_chord} +\title{Minimum frequency distance} +\usage{ +bowl18_min_freq_dist(x) + +\method{bowl18_min_freq_dist}{default}(x) + +\method{bowl18_min_freq_dist}{fr_chord}(x) +} +\arguments{ +\item{x}{Chord to analyse. +The default method assumes that the chord is expressed +as a numeric vector of frequencies. +Representations from the hrep package +(e.g. \code{\link[hrep]{pi_chord}()}) +can be used to analyse chords expressed as MIDI note numbers.} +} +\value{ +(Numeric scalar) +The minimum distance between the fundamental frequencies of the chord, +in Hz. +} +\description{ +This function returns the minimum distance between +the fundamental frequencies of a chord, +after Bowling et al. (2018). +It makes no assumptions about the chord's tuning. +} +\examples{ +bowl18_min_freq_dist(c(220, 440, 560)) # processed as frequencies +bowl18_min_freq_dist(hrep::fr_chord(c(220, 440, 560))) # same as above +bowl18_min_freq_dist(hrep::pi_chord(c(60, 64, 67))) # MIDI note numbers +} +\references{ +\insertRef{Bowling2018}{incon} +} diff --git a/man/complex_sonor.Rd b/man/complex_sonor.Rd new file mode 100644 index 0000000..4c4906f --- /dev/null +++ b/man/complex_sonor.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{complex_sonor} +\alias{complex_sonor} +\alias{complex_sonor.parn94} +\alias{complex_sonor.default} +\title{Get complex sonorousness} +\usage{ +complex_sonor(x, k_c = parn94_params()$k_c, ...) + +\method{complex_sonor}{parn94}(x, k_c = parn94_params()$k_c, ...) + +\method{complex_sonor}{default}(x, k_c = parn94_params()$k_c, ...) +} +\arguments{ +\item{x}{Object to analyse.} + +\item{k_c}{Parncutt & Strasburger (1994) set this to 0.2 (p. 105)} + +\item{...}{Further parameters to pass to \code{\link{parn94}()}.} +} +\value{ +Complex sonorousness, a numeric scalar. +} +\description{ +Computes the complex sonorousness of a sound, after +\insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/corpus_dissonance.Rd b/man/corpus_dissonance.Rd new file mode 100644 index 0000000..c934438 --- /dev/null +++ b/man/corpus_dissonance.Rd @@ -0,0 +1,47 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-corpdiss.R +\name{corpus_dissonance} +\alias{corpus_dissonance} +\title{Corpus dissonance} +\usage{ +corpus_dissonance(x, table = incon::popular_1_pc_chord_type) +} +\arguments{ +\item{x}{Sonority to analyse. +This should be an object created by the \code{hrep} package, +representing a pitch chord (\code{\link[hrep]{pi_chord}}), +representing a pitch-class chord (\code{\link[hrep]{pc_chord}}), +representing a pitch-class chord type (\code{\link[hrep]{pc_chord_type}}), +or a pitch-class set (\code{\link[hrep]{pc_set}}). +This object will be coerced to the same type as the corpus dissonance table, +i.e. \code{type(table)}.} + +\item{table}{Corpus dissonance table, as created by +\code{\link{corpus_dissonance_table}()}. +This table summarises chord prevalences within a corpus. +The default is \code{\link{popular_1_pc_chord_type}}.} +} +\value{ +Dissonance estimate, as a numeric scalar. +} +\description{ +Calculates a corpus-based estimate of the dissonance of a sonority. +} +\details{ +By default, dissonance is estimated from chord prevalences +in the McGill Billboard dataset \insertCite{Burgoyne2011}{hcorp}. +The dataset's contents were sampled from the Billboard magazine's +United States "Hot 100" chart between 1958 and 1991, +and transcribed by expert musicians. +See \code{\link[hcorp]{popular_1}} for more details. + +By default, +the dissonance estimation treats chords as transposition invariant, +and chord pitches as octave-invariant, +but differentiates between different inversions of the same chord. +Different behaviour can be achieved by passing a custom corpus analysis +to the \code{table} argument. +} +\references{ +\insertAllCited{} +} diff --git a/man/corpus_dissonance_table.Rd b/man/corpus_dissonance_table.Rd new file mode 100644 index 0000000..e0d9af2 --- /dev/null +++ b/man/corpus_dissonance_table.Rd @@ -0,0 +1,35 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-corpdiss.R +\name{corpus_dissonance_table} +\alias{corpus_dissonance_table} +\alias{corpus_dissonance_table.corpus} +\title{Corpus dissonance table} +\usage{ +corpus_dissonance_table(x, type = "pc_chord_type", add = 1L) + +\method{corpus_dissonance_table}{corpus}(x, type = "pc_chord_type", add = 1L) +} +\arguments{ +\item{x}{Corpus to analyse, as created by \code{hrep::\link[hrep]{corpus}}.} + +\item{type}{(Scalar character) Representation to which chords should be coerced +before counting.} + +\item{add}{(Scalar numeric) Number to add to each count before computing probabilities. +This is useful for ensuring that chord probabilities exceed zero.} +} +\value{ +Returns an object of class \code{corpus_dissonance_table}, +a \link[tibble]{tibble} where each row corresponds to a +different pitch-class chord type (i.e. an object of class \code{pc_chord_type}), +with the mapping between integer indices and chord types defined by +the \code{hrep} package. +This tibble contains the following columns: +\item{count}{The number of times the chord type was observed in the corpus.} +\item{prob}{The inferred probability of that chord type.} +\item{log_prob}{The inferred log probability of that chord type.} +} +\description{ +Derives sufficient information from a corpus to formulate +a corpus-based dissonance model. +} diff --git a/man/cosine_similarity.Rd b/man/cosine_similarity.Rd new file mode 100644 index 0000000..56a7edf --- /dev/null +++ b/man/cosine_similarity.Rd @@ -0,0 +1,19 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-har18.R +\name{cosine_similarity} +\alias{cosine_similarity} +\title{Cosine similarity} +\usage{ +cosine_similarity(x, y) +} +\arguments{ +\item{x}{Numeric vector 1.} + +\item{y}{Numeric vector 2.} +} +\value{ +Cosine similarity, as a numeric scalar. +} +\description{ +Computes the cosine similarity between two numeric vectors. +} diff --git a/man/count_chords.Rd b/man/count_chords.Rd new file mode 100644 index 0000000..c94507e --- /dev/null +++ b/man/count_chords.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-corpdiss.R +\name{count_chords} +\alias{count_chords} +\alias{count_chords.corpus} +\title{Count chords} +\usage{ +count_chords(x, type = "pc_chord_type") + +\method{count_chords}{corpus}(x, type = "pc_chord_type") +} +\arguments{ +\item{x}{Corpus to analyse.} + +\item{type}{Representation to which chords should be coerced +before counting.} +} +\value{ +Integer vector providing the observed counts for each chord, +indexed by the type encoding defined in the \code{hrep} package. +} +\description{ +This function counts chords within a corpus. +} diff --git a/man/demo_wang.Rd b/man/demo_wang.Rd new file mode 100644 index 0000000..fbf1343 --- /dev/null +++ b/man/demo_wang.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-wang13.R +\name{demo_wang} +\alias{demo_wang} +\title{Wang et al. (2013) interactive demo} +\usage{ +demo_wang(audio = TRUE) +} +\arguments{ +\item{audio}{(Scalar logical) Whether to enable playback controls +(currently the playback controls don't work when the app is hosted +on a remote server).} +} +\description{ +Launches an interactive demo of Wang et al.'s (2013) roughness model. +This function requires a few additional packages to be installed; +you will be notified if any of these packages are missing +once you run \code{demo_wang()}. +} +\note{ +The demo takes the form of an Shiny app +(\url{https://shiny.rstudio.com/}). +} +\references{ +\insertRef{Wang2013}{incon} +} diff --git a/man/dyad_roughness_seth.Rd b/man/dyad_roughness_seth.Rd new file mode 100644 index 0000000..78315e3 --- /dev/null +++ b/man/dyad_roughness_seth.Rd @@ -0,0 +1,60 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{dyad_roughness_seth} +\alias{dyad_roughness_seth} +\title{Dyad roughness (Sethares)} +\usage{ +dyad_roughness_seth( + f1, + f2, + a1, + a2, + ensure_f1_is_less_than_f2 = TRUE, + min_amplitude = TRUE, + a = 3.5, + b = 5.75, + s1 = 0.021, + s2 = 19, + d_star = 0.24 +) +} +\arguments{ +\item{f1}{Frequency of tone 1 (Hz) (numeric vector).} + +\item{f2}{Frequency of tone 2 (Hz) (numeric vector). Must be greater than \code{f1}.} + +\item{a1}{amplitude of tone 1 (numeric vector).} + +\item{a2}{amplitude of tone 2 (numeric vector).} + +\item{ensure_f1_is_less_than_f2}{If \code{FALSE}, assumes that \code{f1 < f2}.} + +\item{min_amplitude}{If \code{TRUE}, +roughness is considered to be proportional to +the minimum amplitude of each pair of partials, +rather than the product of their amplitudes. +The default (\code{TRUE}) corresponds to the algorithm as updated by +\insertCite{Sethares2005;textual}{incon} and +\insertCite{Weisser2013;textual}{incon}. +Set to \code{FALSE} to recover the original algorithm from +\insertCite{Sethares1993;textual}{incon}.} + +\item{a}{Numeric scalar parameter, optimised to 3.5 (default) in Sethares (1993).} + +\item{b}{Numeric scalar parameter, optimised to 5.75 (default) in Sethares (1993).} + +\item{s1}{Numeric scalar parameter from Sethares (1993).} + +\item{s2}{Numeric scalar parameter from Sethares (1993).} + +\item{d_star}{Numeric scalar parameter from Sethares (1993).} +} +\value{ +Numeric vector of roughnesses. +} +\description{ +Gets the roughness of a dyad according to the model of Sethares (1993). +} +\references{ +\insertAllCited{} +} diff --git a/man/dyad_roughness_vass.Rd b/man/dyad_roughness_vass.Rd new file mode 100644 index 0000000..6d67249 --- /dev/null +++ b/man/dyad_roughness_vass.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{dyad_roughness_vass} +\alias{dyad_roughness_vass} +\title{Dyad roughness (Vassilakis)} +\usage{ +dyad_roughness_vass(f1, f2, a1, a2) +} +\arguments{ +\item{f1}{Frequency of tone 1 (Hz) (numeric vector).} + +\item{f2}{Frequency of tone 2 (Hz) (numeric vector).} + +\item{a1}{amplitude of tone 1 (numeric vector).} + +\item{a2}{amplitude of tone 2 (numeric vector).} +} +\value{ +Numeric vector of roughnesses. +} +\description{ +Gets the roughness of a dyad according to the model of +\insertCite{Vassilakis2001;textual}{incon} +\insertCite{Villegas2010;textual}{incon} +} +\note{ +The function is vectorised over all inputs. +} +\references{ +\insertAllCited{} +} diff --git a/man/get_free_field_threshold.Rd b/man/get_free_field_threshold.Rd new file mode 100644 index 0000000..f038891 --- /dev/null +++ b/man/get_free_field_threshold.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{get_free_field_threshold} +\alias{get_free_field_threshold} +\title{Get free field threshold} +\usage{ +get_free_field_threshold(kHz) +} +\arguments{ +\item{kHz}{Numeric vector of frequencies in kHz.} +} +\value{ +Numeric vector of corresponding free-field thresholds in dB SPL. +} +\description{ +Returns the free-field threshold (dB SPL) of hearing in quiet for pure tones +of given frequencies. +This is the minimum sound level at which a pure tone at that frequency +will be heard. +Corresponds to Equation 2 in \insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/get_overall_masking_level.Rd b/man/get_overall_masking_level.Rd new file mode 100644 index 0000000..010372e --- /dev/null +++ b/man/get_overall_masking_level.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{get_overall_masking_level} +\alias{get_overall_masking_level} +\title{Get overall masking level} +\usage{ +get_overall_masking_level(auditory_level, pure_tone_height, k_m) +} +\arguments{ +\item{auditory_level}{Numeric vector of auditory levels} + +\item{pure_tone_height}{Numeric vector of pure tone heights} + +\item{k_m}{See \code{\link{parn94_params}()}.} +} +\value{ +Numeric vector of overall masking levels (dB). +} +\description{ +Returns overall masking levels for a set of pure tones +that are assumed to be playing simultaneously. +Corresponds to Parncutt & Strasburger (1994) Equation 5. +} +\references{ +\insertAllCited{} +} diff --git a/man/get_partial_masking_level.Rd b/man/get_partial_masking_level.Rd new file mode 100644 index 0000000..e929df2 --- /dev/null +++ b/man/get_partial_masking_level.Rd @@ -0,0 +1,41 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{get_partial_masking_level} +\alias{get_partial_masking_level} +\title{Get partial masking level} +\usage{ +get_partial_masking_level( + masker_auditory_level, + masker_pure_tone_height, + maskee_auditory_level, + maskee_pure_tone_height, + k_m +) +} +\arguments{ +\item{masker_auditory_level}{Numeric vector of masker auditory levels.} + +\item{masker_pure_tone_height}{Numeric vector of masker pure tone heights.} + +\item{maskee_auditory_level}{Numeric vector of maskee auditory levels.} + +\item{maskee_pure_tone_height}{Numeric vector of maskee pure tone heights.} + +\item{k_m}{Parameter \code{k_m} in \insertCite{Parncutt1994;textual}{incon}. +represents the masking pattern gradient for a pure tone, +with units of dB per critical band. +Parncutt & Strasburger use a value of 12 in their examples, +but imply that 12-18 is a typical range of values for this parameter.} +} +\value{ +Matrix where element [i, j] gives the level of masking +for masker j on maskee i. +} +\description{ +Returns the effective reduction in dB of the audible level +of a masked pure tone (maskee) on account of a masking pure tone (masker). +Equation 4 in \insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/get_pure_tone_audibility.Rd b/man/get_pure_tone_audibility.Rd new file mode 100644 index 0000000..a8b0a19 --- /dev/null +++ b/man/get_pure_tone_audibility.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{get_pure_tone_audibility} +\alias{get_pure_tone_audibility} +\title{Get audibility} +\usage{ +get_pure_tone_audibility(pure_tone_audible_level, al_0) +} +\arguments{ +\item{pure_tone_audible_level}{Numeric vector of audible levels (dB).} + +\item{al_0}{constant (see Equation 7 of Parncutt & Strasburger (1994)).} +} +\value{ +Numeric vector of pure tone audibilities. +} +\description{ +Returns the audibility of a set of pure tone components as a +function of their audible levels. +Corresponds to Equation 7 of \insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/get_pure_tone_audible_level.Rd b/man/get_pure_tone_audible_level.Rd new file mode 100644 index 0000000..c06e5d9 --- /dev/null +++ b/man/get_pure_tone_audible_level.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{get_pure_tone_audible_level} +\alias{get_pure_tone_audible_level} +\title{Get audible level} +\usage{ +get_pure_tone_audible_level(auditory_level, overall_masking_level) +} +\arguments{ +\item{auditory_level}{Numeric vector of auditory levels for +a set of pure tones (dB).} + +\item{overall_masking_level}{Numeric vector of overall masking levels +for a set of pure tones (dB).} +} +\value{ +Numeric vector of audible levels (dB). +} +\description{ +Returns the audible level for set of pure tones subject +to a given masking pattern. +Corresponds to Equation 6 of \insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/get_pure_tone_height.Rd b/man/get_pure_tone_height.Rd new file mode 100644 index 0000000..977cd59 --- /dev/null +++ b/man/get_pure_tone_height.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{get_pure_tone_height} +\alias{get_pure_tone_height} +\title{Get pure-tone height} +\usage{ +get_pure_tone_height(kHz) +} +\arguments{ +\item{kHz}{Numeric vector of frequencies in kHz.} +} +\value{ +Numeric vector of corresponding pure-tone heights, +with units of equivalent rectangular bandwidths (ERBs). +} +\description{ +Returns the pure-tone heights (a.k.a. critical-band rates) +of pure tones of given frequencies. +Equation 3 in \insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/get_tone_salience.Rd b/man/get_tone_salience.Rd new file mode 100644 index 0000000..42de079 --- /dev/null +++ b/man/get_tone_salience.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{get_tone_salience} +\alias{get_tone_salience} +\title{Get tone salience} +\usage{ +get_tone_salience(combined_audibility, k_s) +} +\arguments{ +\item{combined_audibility}{Numeric vector corresponding to the +audibilities of each tone in the combined (pure and complex) spectrum.} + +\item{k_s}{Numeric scalar; \insertCite{Parncutt1994;textual}{incon} +set this to 0.5.} +} +\value{ +Numeric vector of saliences of the same length as +\code{combined_audibility}, giving the salience of each respective tone. +} +\description{ +Gets the salience of different tones within a sonority with +reference to the combined (complex and pure) spectrum. +Salience can be interpreted as the probability of consciously +perceiving a given pitch. +} +\references{ +\insertAllCited{} +} diff --git a/man/gill09_harmonicity.Rd b/man/gill09_harmonicity.Rd new file mode 100644 index 0000000..94e9def --- /dev/null +++ b/man/gill09_harmonicity.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-bowl18.R +\name{gill09_harmonicity} +\alias{gill09_harmonicity} +\title{Harmonicity} +\usage{ +gill09_harmonicity(x, tonic = 0L) +} +\arguments{ +\item{x}{Chord to analyse, +expressed as an integerish vector of MIDI note numbers, +or more generally as any valid input to \code{\link[hrep:pi_chord]{pi_chord}}.} + +\item{tonic}{(Integerish scalar, default = 0L) +Tonic to use when defining the just-tuned scale.} +} +\value{ +(Numeric scalar) +The chord's harmonicity, defined as the proportion of harmonics +of the chord's fundamental frequency that coincide with the +harmonics of the chord tones. +} +\description{ +Computes the harmonicity of a chord using the +percentage similarity measure of Gill & Purves (2009). +} +\details{ +This percentage similarity measure corresponds to the percentage of the harmonics +that the chord holds in common with a harmonic series rooted on +the chord's fundamental frequency. + +While \insertCite{Gill2009;textual}{incon} originally presented this measure +to quantify the harmonicity of two-note chords (dyads) +\insertCite{Bowling2018;textual}{incon} subsequently demonstrated the application +of this measure to chords of arbitrary sizes. +} +\note{ +The algorithm assumes that chord pitches are precisely +tuned to the just-tuned scale provided by Bowling et al. (2018). +This scale can be found at \code{\link{rational_scale}}. +} +\examples{ +gill09_harmonicity(c(60, 64, 67)) +gill09_harmonicity("60 64 67") +gill09_harmonicity(hrep::pi_chord(c(60, 64, 67))) +} +\references{ +\insertRef{Bowling2018}{incon} +} diff --git a/man/har_19_composite.Rd b/man/har_19_composite.Rd index 19e3461..8cda088 100644 --- a/man/har_19_composite.Rd +++ b/man/har_19_composite.Rd @@ -1,11 +1,10 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/har-2019.R +% Please edit documentation in R/model-har-2019.R \name{har_19_composite} \alias{har_19_composite} \title{Composite consonance model} \usage{ -har_19_composite(x, chord_size = FALSE, num_harmonics = 11L, - roll_off = 1) +har_19_composite(x, chord_size = FALSE, num_harmonics = 11L, roll_off = 1) } \arguments{ \item{x}{Chord to analyse; passed to \code{\link{incon}}. @@ -32,22 +31,22 @@ composite consonance model. The model combines several sub-models: \itemize{ \item \code{hutch_78_roughness}, -the roughness model of \insertCite{Hutchinson1978;textual}{dycon} -(see \code{dycon::\link[dycon]{roughness_hutch}}); +the roughness model of \insertCite{Hutchinson1978;textual}{incon} +(see \code{incon::\link[incon]{roughness_hutch}}); \item \code{har_18_harmonicity}, -the harmonicity model of \insertCite{Harrison2018;textual}{har18} -(see \code{har18::\link[har18]{pc_harmonicity}}); +the harmonicity model of \insertCite{Harrison2018;textual}{incon} +(see \code{incon::\link[incon]{pc_harmonicity}}); \item \code{har_19_corpus}: a corpus-based model of cultural familiarity (Harrison & Pearce, in preparation) -(see \code{corpdiss::\link[corpdiss]{corpus_dissonance}}). +(see \code{incon::\link[incon]{corpus_dissonance}}). } This model uses the regression coefficients provided in \code{\link{har_19_composite_coef}}, with one caveat: by default, the chord size effect is disabled, because it's thought that this effect came from a confound -in the perceptual data of \insertCite{Bowling2018;textual}{bowl18}. +in the perceptual data of \insertCite{Bowling2018;textual}{incon}. } \references{ \insertAllCited{} diff --git a/man/har_19_composite_coef.Rd b/man/har_19_composite_coef.Rd index 08fe991..5f4afa7 100644 --- a/man/har_19_composite_coef.Rd +++ b/man/har_19_composite_coef.Rd @@ -1,10 +1,12 @@ % Generated by roxygen2: do not edit by hand -% Please edit documentation in R/har-2019.R +% Please edit documentation in R/model-har-2019.R \docType{data} \name{har_19_composite_coef} \alias{har_19_composite_coef} \title{Regression coefficients} -\format{An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 5 rows and 2 columns.} +\format{ +An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 5 rows and 2 columns. +} \usage{ har_19_composite_coef } diff --git a/man/huron_1994.Rd b/man/huron_1994.Rd new file mode 100644 index 0000000..b6e1e2b --- /dev/null +++ b/man/huron_1994.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-huron-1994.R +\name{huron_1994} +\alias{huron_1994} +\title{Dyadic consonance (Huron 1994)} +\usage{ +huron_1994(x) +} +\arguments{ +\item{x}{Chord to evaluate, will be coerced to a +pitch-class set (\code{\link[hrep]{pc_set}}).} +} +\description{ +Computes the aggregate dyadic consonance of a chord following +Huron (1994). +} diff --git a/man/huron_1994_weights.Rd b/man/huron_1994_weights.Rd new file mode 100644 index 0000000..99134a9 --- /dev/null +++ b/man/huron_1994_weights.Rd @@ -0,0 +1,16 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-huron-1994.R +\docType{data} +\name{huron_1994_weights} +\alias{huron_1994_weights} +\title{Dyadic consonance weights (Huron 1994)} +\format{ +An object of class \code{numeric} of length 6. +} +\usage{ +huron_1994_weights +} +\description{ +A vector of the weights used in Huron's (1994) dyadic consonance model. +} +\keyword{datasets} diff --git a/man/hutch_cbw.Rd b/man/hutch_cbw.Rd new file mode 100644 index 0000000..9defa80 --- /dev/null +++ b/man/hutch_cbw.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{hutch_cbw} +\alias{hutch_cbw} +\title{Critical bandwidth (Hutchison & Knopoff)} +\usage{ +hutch_cbw(f1, f2) +} +\arguments{ +\item{f1}{(Numeric vector) Frequency 1, Hz} + +\item{f2}{(Numeric vector) Frequency 2, Hz} +} +\value{ +(Numeric vector) Critical bandwidths. +} +\description{ +Calculates the critical bandwidth given pairs of frequencies +\code{f1} and \code{f2}, +according to the model of \insertCite{Hutchinson1978;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/hutch_dissonance_function.Rd b/man/hutch_dissonance_function.Rd new file mode 100644 index 0000000..ed6569c --- /dev/null +++ b/man/hutch_dissonance_function.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{hutch_dissonance_function} +\alias{hutch_dissonance_function} +\title{Get dissonance contribution} +\usage{ +hutch_dissonance_function(f1, f2, cbw_cut_off = 1.2, a = 0.25, b = 2) +} +\arguments{ +\item{f1}{(Numeric vector) Frequency 1, Hz} + +\item{f2}{(Numeric vector) Frequency 2, Hz} + +\item{cbw_cut_off}{Parameter passed to \code{\link{hutch_g}()}.} + +\item{a}{Parameter passed to \code{\link{hutch_g}()}.} + +\item{b}{Parameter passed to \code{\link{hutch_g}()}.} +} +\value{ +Numeric vector of dissonance contributions. +} +\description{ +Computes the dissonance contribution of a pair of pure tones. +} diff --git a/man/hutch_g.Rd b/man/hutch_g.Rd new file mode 100644 index 0000000..751efbd --- /dev/null +++ b/man/hutch_g.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{hutch_g} +\alias{hutch_g} +\title{Dissonance factor (Hutchinson & Knopoff)} +\usage{ +hutch_g(y, cbw_cut_off = 1.2, a = 0.25, b = 2) +} +\arguments{ +\item{y}{(Numeric vector) Frequency distance in units of critical bandwidths.} + +\item{cbw_cut_off}{(Numeric scalar) +If not \code{NULL}, then should be a number +corresponding to the variable CBWcutoff in Mashinter's own implementation. +If \code{y >= cbw_cut_off}, then the dissonance factor will be approximated as 0. +Setting \code{cbw_cut_off} to 1.2 is necessary for replicating Mashinter's results. +A cut-off of 1.2 was also used by \insertCite{Bigand1996;textual}{incon}.} + +\item{a}{(Numeric scalar, default = 0.25) +Parameter from \insertCite{Mashinter2006;textual}{incon}.} + +\item{b}{(Numeric scalar, default = 2) +Parameter from \insertCite{Mashinter2006;textual}{incon}.} +} +\value{ +(Numeric vector) Dissonance factors. +} +\description{ +Computes dissonance factors given frequency distances in units of +critical bandwidths, after Mashinter's implementation of +Hutchinson & Knopoff's model +\insertCite{Mashinter2006,Hutchinson1978}{incon}. +This function corresponds to an approximation of the +look-up table in \insertCite{Plomp1965;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/hutch_visualise.Rd b/man/hutch_visualise.Rd new file mode 100644 index 0000000..afac64e --- /dev/null +++ b/man/hutch_visualise.Rd @@ -0,0 +1,45 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{hutch_visualise} +\alias{hutch_visualise} +\title{Visualise} +\usage{ +hutch_visualise( + x, + cbw_cut_off = 1.2, + a = 0.25, + b = 2, + label = "Roughness", + amplitude_breaks = c(0, 1), + colour_limits = c(0, 3), + colour_low = "darkblue", + colour_high = "red", + ... +) +} +\arguments{ +\item{x}{Passed to \code{\link{roughness_hutch}}.} + +\item{cbw_cut_off}{Passed to \code{\link{roughness_hutch}}.} + +\item{a}{Passed to \code{\link{roughness_hutch}}.} + +\item{b}{Passed to \code{\link{roughness_hutch}}.} + +\item{label}{(Character scalar) x-axis label.} + +\item{amplitude_breaks}{Numeric vector of tick locations for the y-axis.} + +\item{colour_limits}{Defines the limits of the roughness scale.} + +\item{colour_low}{Colour to use for the lowest roughness.} + +\item{colour_high}{Colour to use for the highest roughness.} + +\item{...}{Passed to \code{\link[hrep]{sparse_fr_spectrum}}.} + +\item{theme}{\code{\link[ggplot2]{ggplot}} theme to use.} +} +\description{ +Creates a plot visualising computations for Hutchinson & Knopoff's model. +} diff --git a/man/hutch_y.Rd b/man/hutch_y.Rd new file mode 100644 index 0000000..74f35fb --- /dev/null +++ b/man/hutch_y.Rd @@ -0,0 +1,24 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{hutch_y} +\alias{hutch_y} +\title{Critical bandwidth distance (Hutchison & Knopoff)} +\usage{ +hutch_y(f1, f2) +} +\arguments{ +\item{f1}{(Numeric vector) Frequency 1, Hz} + +\item{f2}{(Numeric vector) Frequency 2, Hz} +} +\value{ +(Numeric vector) Unsigned distances in frequency bandwidths. +} +\description{ +Calculates the distance between pairs of frequencies in units of +critical bandwidths, according to the model of +\insertCite{Hutchinson1978;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/incon.Rd b/man/incon.Rd index a9abc5e..2213ff4 100644 --- a/man/incon.Rd +++ b/man/incon.Rd @@ -4,8 +4,14 @@ \alias{incon} \title{Simultaneous consonance} \usage{ -incon(x, model = "hutch_78_roughness", x_int = NULL, - num_harmonics = 11L, roll_off = 1, par = list()) +incon( + x, + model = "hutch_78_roughness", + x_int = NULL, + num_harmonics = 11L, + roll_off = 1, + par = list() +) } \arguments{ \item{x}{Chord to analyse. @@ -61,58 +67,58 @@ according to various computational models. The following models are available: \itemize{ \item \code{gill_09_harmonicity}: -the harmonicity model of \insertCite{Gill2009;textual}{bowl18} -(see \code{bowl18::\link[bowl18]{gill09_harmonicity}}). +the harmonicity model of \insertCite{Gill2009;textual}{incon} +(see \code{incon::\link[incon]{gill09_harmonicity}}). \item \code{har_18_harmonicity}: -the harmonicity model of \insertCite{Harrison2018;textual}{har18} -(see \code{har18::\link[har18]{pc_harmonicity}}). +the harmonicity model of \insertCite{Harrison2018;textual}{incon} +(see \code{incon::\link[incon]{pc_harmonicity}}). \item \code{milne_13_harmonicity}: -the harmonicity model of \insertCite{Milne2013;textual}{har18} -(see \code{har18::\link[har18]{pc_harmonicity}}). +the harmonicity model of \insertCite{Milne2013;textual}{incon} +(see \code{incon::\link[incon]{pc_harmonicity}}). \item \code{parn_88_root_ambig}: -the root ambiguity model of \insertCite{Parncutt1988;textual}{parn88} -(see \code{parn88::\link[parn88]{root_ambiguity}}). +the root ambiguity model of \insertCite{Parncutt1988;textual}{incon} +(see \code{incon::\link[incon]{root_ambiguity}}). \item \code{parn_94_complex}: -the complex sonorousness feature of \insertCite{Parncutt1994;textual}{parn94} -(see \code{parn94::\link[parn94]{complex_sonor}}). +the complex sonorousness feature of \insertCite{Parncutt1994;textual}{incon} +(see \code{incon::\link[incon]{complex_sonor}}). \item \code{stolz_15_periodicity}: smoothed logarithmic periodicity, -after \insertCite{Stolzenburg2015;textual}{stolz15} -(see \code{stolz15::\link[stolz15]{smooth_log_periodicity}}). +after \insertCite{Stolzenburg2015;textual}{incon} +(see \code{incon::\link[incon]{smooth_log_periodicity}}). \item \code{bowl_18_min_freq_dist}: the minimum frequency distance feature of -\insertCite{Bowling2018;textual}{bowl18} -(see \code{bowl18::\link[bowl18]{bowl18_min_freq_dist}}). +\insertCite{Bowling2018;textual}{incon} +(see \code{incon::\link[incon]{bowl18_min_freq_dist}}). \item \code{huron_94_dyadic}: aggregate dyadic consonance, after \insertCite{Huron1994;textual}{incon}. \item \code{hutch_78_roughness}: -the roughness model of \insertCite{Hutchinson1978;textual}{dycon} -(see \code{dycon::\link[dycon]{roughness_hutch}}). +the roughness model of \insertCite{Hutchinson1978;textual}{incon} +(see \code{incon::\link[incon]{roughness_hutch}}). \item \code{parn_94_pure}: -the complex sonorousness feature of \insertCite{Parncutt1994;textual}{parn94} -(see \code{parn94::\link[parn94]{pure_sonor}}). +the complex sonorousness feature of \insertCite{Parncutt1994;textual}{incon} +(see \code{incon::\link[incon]{pure_sonor}}). \item \code{seth_93_roughness}: -the roughness model of \insertCite{Sethares1993;textual}{dycon} -(see \code{dycon::\link[dycon]{roughness_seth}}). +the roughness model of \insertCite{Sethares1993;textual}{incon} +(see \code{incon::\link[incon]{roughness_seth}}). \item \code{vass_01_roughness}: -the roughness model of \insertCite{Vassilakis2001;textual}{dycon} -(see \code{dycon::\link[dycon]{roughness_vass}}). +the roughness model of \insertCite{Vassilakis2001;textual}{incon} +(see \code{incon::\link[incon]{roughness_vass}}). \item \code{wang_13_roughness}: -the roughness model of \insertCite{Wang2013;textual}{wang13} -(see \code{wang13::\link[wang13]{roughness_wang}}). +the roughness model of \insertCite{Wang2013;textual}{incon} +(see \code{incon::\link[incon]{roughness_wang}}). \item \code{jl_12_tonal}: -the tonal dissonance model of \insertCite{Johnson-Laird2012;textual}{jl12} -(see \code{jl12::\link[jl12]{jl_tonal_dissonance}}). +the tonal dissonance model of \insertCite{Johnson-Laird2012;textual}{incon} +(see \code{incon::\link[incon]{jl_tonal_dissonance}}). \item \code{har_19_corpus}: a corpus-based model of cultural familiarity \insertCite{Harrison2019}{incon} -(see \code{corpdiss::\link[corpdiss]{corpus_dissonance}}). +(see \code{incon::\link[incon]{corpus_dissonance}}). \item \code{parn_94_mult}: -the multiplicity feature of \insertCite{Parncutt1994;textual}{parn94} -(see \code{parn94::\link[parn94]{multiplicity}}). +the multiplicity feature of \insertCite{Parncutt1994;textual}{incon} +(see \code{incon::\link[incon]{multiplicity}}). \item \code{har_19_composite}: -a model combining interference \insertCite{Hutchinson1978}{dycon}, -periodicity/harmonicity \insertCite{Harrison2018}{har18}, +a model combining interference \insertCite{Hutchinson1978}{incon}, +periodicity/harmonicity \insertCite{Harrison2018}{incon}, and cultural familiarity, as introduced by \insertCite{Harrison2019;textual}{incon}. } diff --git a/man/incon_models.Rd b/man/incon_models.Rd index ff38191..05a3d7d 100644 --- a/man/incon_models.Rd +++ b/man/incon_models.Rd @@ -4,7 +4,9 @@ \name{incon_models} \alias{incon_models} \title{Simultaneous consonance models} -\format{An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 17 rows and 8 columns.} +\format{ +An object of class \code{tbl_df} (inherits from \code{tbl}, \code{data.frame}) with 17 rows and 8 columns. +} \usage{ incon_models } diff --git a/man/jl_rule_1.Rd b/man/jl_rule_1.Rd new file mode 100644 index 0000000..7974062 --- /dev/null +++ b/man/jl_rule_1.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-jl12.R +\name{jl_rule_1} +\alias{jl_rule_1} +\title{Tonal Dissonance, Rule 1} +\usage{ +jl_rule_1(pc_set) +} +\arguments{ +\item{pc_set}{(Numeric vector) Pitch-class set to analyse. +No input checking is performed.} +} +\value{ +1, 2, or 3, corresponding to increasing degrees of dissonance. +} +\description{ +"Chords occurring in a major scale should be less dissonant +than chords occurring only in a minor scale, +which in turn should be less dissonant than chords +occurring in neither sort of scale." +\insertCite{Johnson-Laird2012}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/jl_rule_2.Rd b/man/jl_rule_2.Rd new file mode 100644 index 0000000..2f33c8b --- /dev/null +++ b/man/jl_rule_2.Rd @@ -0,0 +1,26 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-jl12.R +\name{jl_rule_2} +\alias{jl_rule_2} +\title{Tonal Dissonance, Rule 2} +\usage{ +jl_rule_2(pc_set) +} +\arguments{ +\item{pc_set}{(Numeric vector) Pitch-class set to analyse. +No input checking is performed.} +} +\value{ +\code{TRUE} for dissonant, \code{FALSE} for consonant. +} +\description{ +"Chords that are consistent with a major triad are more consonant +than chords that are not consistent with a major triad" +\insertCite{Johnson-Laird2012}{incon}. +Consistency with the major triad means that a major triad +must be contained within the chord. +Additionally, all notes must be contained within a major scale. +} +\references{ +\insertAllCited{} +} diff --git a/man/jl_rule_3.Rd b/man/jl_rule_3.Rd new file mode 100644 index 0000000..52aa309 --- /dev/null +++ b/man/jl_rule_3.Rd @@ -0,0 +1,28 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-jl12.R +\name{jl_rule_3} +\alias{jl_rule_3} +\title{Tonal Dissonance, Rule 3} +\usage{ +jl_rule_3(pc_set) +} +\arguments{ +\item{pc_set}{(Numeric vector) Pitch-class set to analyse.} +} +\value{ +\code{FALSE} for consonant, \code{TRUE} for dissonant. +If a consonant solution is found, +the stacked-thirds version of the chord is returned +as the attribute \code{"solution"}, +accessible with the command \code{attr(x, "solution")}. +} +\description{ +"Chords built from intervals of a third should be more consonant +than chords that are not built from thirds." +"The principle allows for just one missing third intervening +between two pitch classes a fifth apart" +\insertCite{Johnson-Laird2012}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/jl_tonal_dissonance.Rd b/man/jl_tonal_dissonance.Rd new file mode 100644 index 0000000..9c4dbe8 --- /dev/null +++ b/man/jl_tonal_dissonance.Rd @@ -0,0 +1,34 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-jl12.R +\name{jl_tonal_dissonance} +\alias{jl_tonal_dissonance} +\alias{jl_tonal_dissonance.default} +\alias{jl_tonal_dissonance.pc_set} +\title{Tonal dissonance} +\usage{ +jl_tonal_dissonance(x) + +\method{jl_tonal_dissonance}{default}(x) + +\method{jl_tonal_dissonance}{pc_set}(x) +} +\arguments{ +\item{x}{Sonority to analyse. +This will be coerced to an object of class \code{\link[hrep]{pc_set}}.} +} +\value{ +Integer scalar identifying the chord's consonance rank, +with higher values corresponding to increasing degrees of dissonance. +} +\description{ +Computes tonal dissonance using the algorithm +of \insertCite{Johnson-Laird2012;textual}{incon}. +} +\examples{ +jl_tonal_dissonance(c(0, 4, 7)) +jl_tonal_dissonance(c(0, 3, 7)) +jl_tonal_dissonance(c(0, 3, 6)) +} +\references{ +\insertAllCited{} +} diff --git a/man/kl_div_from_uniform.Rd b/man/kl_div_from_uniform.Rd new file mode 100644 index 0000000..5c2478c --- /dev/null +++ b/man/kl_div_from_uniform.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-har18.R +\name{kl_div_from_uniform} +\alias{kl_div_from_uniform} +\alias{kl_div_from_uniform.smooth_spectrum} +\title{Kullback-Leibler divergence from uniform} +\usage{ +kl_div_from_uniform(x) + +\method{kl_div_from_uniform}{smooth_spectrum}(x) +} +\arguments{ +\item{x}{Input distribution.} +} +\value{ +The Kullback-Leibler divergence from a uniform distribution +to the input distribution. +} +\description{ +Gets the Kullback-Leibler divergence of a provided distribution +from a uniform distribution. +} diff --git a/man/multiplicity.Rd b/man/multiplicity.Rd new file mode 100644 index 0000000..e71f5c3 --- /dev/null +++ b/man/multiplicity.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{multiplicity} +\alias{multiplicity} +\alias{multiplicity.parn94} +\alias{multiplicity.default} +\title{Get multiplicity} +\usage{ +multiplicity(x, k_s = parn94_params()$k_s, ...) + +\method{multiplicity}{parn94}(x, k_s = parn94_params()$k_s, ...) + +\method{multiplicity}{default}(x, k_s = parn94_params()$k_s, ...) +} +\arguments{ +\item{x}{Object to analyse.} + +\item{k_s}{Numeric scalar, parameter from Parncutt & Strasburger (1994).} + +\item{...}{Further parameters to pass to \code{\link{incon}()}.} +} +\value{ +Multiplicity, a numeric scalar. +} +\description{ +Computes the multiplicity of a sound, after +\insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/parn88.Rd b/man/parn88.Rd new file mode 100644 index 0000000..fd2bb43 --- /dev/null +++ b/man/parn88.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn88.R +\name{parn88} +\alias{parn88} +\alias{parn88.default} +\alias{parn88.pc_set} +\title{Parncutt (1988)} +\usage{ +parn88(x, root_support = "v2", exponent = 0.5) + +\method{parn88}{default}(x, root_support = "v2", exponent = 0.5) + +\method{parn88}{pc_set}(x, root_support = "v2", exponent = 0.5) +} +\arguments{ +\item{x}{Sonority to analyse. +This will be coerced to an object of class \code{\link[hrep]{pc_set}}.} + +\item{root_support}{(Character scalar or data frame) +Identifies the root support weights to use. +\itemize{ +\item \code{"v2"} (default) uses the updated +weights from \insertCite{Parncutt2006;textual}{incon}. +\item \code{"v1"} uses the original weights from \insertCite{Parncutt2006;textual}{incon}. +} + +See \code{\link{root_support_weights}} for the values of these weights. +Alternatively, root-support weights can be provided as a data frame, +with one column (interval) identifying the ascending interval in semitones, +and another column (weight) identifying the corresponding root support weight.} + +\item{exponent}{(Numeric scalar) Exponent to be used when computing +root ambiguities. Defaults to 0.5, after \insertCite{Parncutt1988;textual}{incon}.} +} +\value{ +A list with three values: +\itemize{ +\item \code{root}, the estimated chord root (integer scalar); +\item \code{root_ambiguity}, the root ambiguity (numeric scalar), +\item \code{pc_weight}, a 12-dimensional vector of weights by pitch class. +} +} +\description{ +Analyses a pitch-class set using the root-finding model of +\insertCite{Parncutt1988;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/parn94.Rd b/man/parn94.Rd new file mode 100644 index 0000000..ebb20ea --- /dev/null +++ b/man/parn94.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{parn94} +\alias{parn94} +\alias{parn94.default} +\alias{parn94.sparse_pi_spectrum} +\title{Parncutt & Strasburger (1994)} +\usage{ +parn94(x, par = parn94_params(), ...) + +\method{parn94}{default}(x, par = parn94_params(), ...) + +\method{parn94}{sparse_pi_spectrum}(x, par = parn94_params(), ...) +} +\arguments{ +\item{x}{Object to analyse, +which will be coerced to an object of class +\code{\link[hrep]{sparse_pi_spectrum}}. +Various input types are possible: +\itemize{ +\item Numeric vectors will be treated as vectors of MIDI note numbers, +which will be expanded into their implied harmonics. +\item A two-element list can be used to define a harmonic spectrum. +The first element should be a vector of MIDI note numbers, +the second a vector of amplitudes. +\item The function also accepts classes from the \code{hrep} package, +such as produced by \code{\link[hrep]{pi_chord}()} and +\code{\link[hrep]{sparse_pi_spectrum}()}. +}} + +\item{par}{Parameter list as created by \code{\link{parn94_params}()}.} + +\item{...}{Parameters to pass to \code{\link[hrep]{sparse_pi_spectrum}}. +\itemize{ +\item \code{num_harmonics}: Number of harmonics to use when expanding +chord tones into their implied harmonics. +\item \code{roll_off}: Rate of amplitude roll-off for the harmonics. +}} +} +\value{ +An list of class \code{parn94}, comprising the following components: +\item{pure_spectrum}{A tibble describing the sonority's pure spectrum. +The pure spectrum is a spectral representation of the input sound +after auditory masking, but before pattern matching.} +\item{pure_spectrum}{A tibble describing the sonority's complex spectrum. +The complex spectrum is created from the pure spectrum through +harmonic template matching.} +\item{combined_spectrum}{A tibble describing the sonority's combined spectrum. +The combined spectrum corresponds to the combination of the +pure and complex spectra.} +\item{par}{A list comprising the parameters used to perform the analysis, +as created by \code{\link{parn94_params}()}.} +} +\description{ +This function analyses a sonority using Richard Parncutt's +psychoacoustic model of harmony, as described in +\insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/parn94_params.Rd b/man/parn94_params.Rd new file mode 100644 index 0000000..4fda06b --- /dev/null +++ b/man/parn94_params.Rd @@ -0,0 +1,62 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{parn94_params} +\alias{parn94_params} +\title{Model parameters} +\usage{ +parn94_params( + unit_amplitude_in_dB = 60, + template_num_harmonics = 11, + template_roll_off = 1, + k_t = 3, + k_p = 0.5, + k_c = 0.2, + k_s = 0.5, + k_m = 12, + al_0 = 15, + min_midi = 0, + max_midi = 120 +) +} +\arguments{ +\item{unit_amplitude_in_dB}{(Numeric scalar) Describes the number of decibels +to assign to a spectral component of amplitude 1. +By default, amplitude 1 corresponds to the amplitude of each chord pitch's +fundamental frequency (though this can be changed by expressing input chords +as pitch-class sparse spectra, see \code{\link[hrep]{sparse_pi_spectrum}()}).} + +\item{template_num_harmonics}{(Integerish scalar) Number of harmonics to +include in the spectral template, including the fundamental frequency.} + +\item{template_roll_off}{(Numeric scalar) Roll-off rate for the +harmonic template. This parameter is passed to +\code{\link[hrep]{sparse_pi_spectrum}()}.} + +\item{k_t}{(Numeric scalar) Relative perceptual weighting of complex versus pure tones.} + +\item{k_p}{(Numeric scalar) Scaling coefficient for pure sonorousness.} + +\item{k_c}{(Numeric scalar) Scaling coefficient for complex sonorousness.} + +\item{k_s}{(Numeric scalar) Scaling coefficient for multiplicity.} + +\item{k_m}{(Numeric scalar) Gradient of a pure tone's masking pattern.} + +\item{al_0}{(Numeric scalar) Audibility saturation rate.} + +\item{min_midi}{(Numeric scalar) Lowest MIDI pitch considered by the model.} + +\item{max_midi}{(Numeric scalar) Highest MIDI pitch considered by the model.} +} +\value{ +A list of parameters which can be passed to analysis functions +such as \code{\link{parn94}}. +} +\description{ +This function compiles parameters for Parncutt's psychoacoustic model. +The parameters are defined with reference to +\insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/pc_harmonicity.Rd b/man/pc_harmonicity.Rd new file mode 100644 index 0000000..6c9c7e6 --- /dev/null +++ b/man/pc_harmonicity.Rd @@ -0,0 +1,102 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-har18.R +\name{pc_harmonicity} +\alias{pc_harmonicity} +\alias{pc_harmonicity.default} +\alias{pc_harmonicity.pc_set} +\alias{pc_harmonicity.milne_pc_spectrum} +\title{Pitch-class harmonicity} +\usage{ +pc_harmonicity( + x, + method = "kl", + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + ... +) + +\method{pc_harmonicity}{default}( + x, + method = "kl", + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + array_dim = 1200, + ... +) + +\method{pc_harmonicity}{pc_set}( + x, + method = "kl", + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + array_dim = 1200, + ... +) + +\method{pc_harmonicity}{milne_pc_spectrum}( + x, + method = "kl", + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + ... +) +} +\arguments{ +\item{x}{Object to analyse.} + +\item{method}{(Character scalar) Method to use. +\itemize{ +\item \code{"kl"} (default) delivers the Kullback-Leibler method of +\insertCite{Harrison2018;textual}{incon}. +\item \code{"peak"} delivers the peak-value method of +\insertCite{Milne2013;textual}{incon}. +}} + +\item{num_harmonics}{(Integerish scalar) +Number of harmonics to use when expanding tones into their implied harmonics, +and when defining the harmonic template +(including the fundamental frequency). +Defaults to 12, after +\insertCite{Milne2016;textual}{incon}.} + +\item{rho}{(Numeric scalar) +Roll-off parameter for harmonic expansion. +Defaults to 0.75, after +\insertCite{Milne2016;textual}{incon}.} + +\item{sigma}{(Numeric scalar) +Standard deviation of the Gaussian smoothing distribution (cents). +Defaults to 6.83, after +\insertCite{Milne2016;textual}{incon}.} + +\item{...}{Arguments passed to specific methods.} + +\item{array_dim}{(Integerish scalar) +Dimensionality of the pitch-class spectrum array. +Defaults to 1200, after +\insertCite{Milne2016;textual}{incon}.} +} +\value{ +Pitch-class harmonicity, as a numeric scalar. +} +\description{ +Gets the pitch-class harmonicity of an input sonority, after +\insertCite{Harrison2018;textual}{incon} and +\insertCite{Milne2013;textual}{incon}. +} +\note{ +This algorithm makes use of \code{\link[hrep]{milne_pc_spectrum}()} +as defined in the \code{hrep} package. +} +\examples{ +pc_harmonicity(c(0, 4, 7)) +pc_harmonicity(c(0, 3, 7)) +pc_harmonicity(c(0, 3, 6)) +} +\references{ +\insertAllCited{} +} diff --git a/man/peak.Rd b/man/peak.Rd new file mode 100644 index 0000000..6124cc0 --- /dev/null +++ b/man/peak.Rd @@ -0,0 +1,20 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-har18.R +\name{peak} +\alias{peak} +\alias{peak.milne_pc_spectrum} +\title{Peak} +\usage{ +peak(x) + +\method{peak}{milne_pc_spectrum}(x) +} +\arguments{ +\item{x}{Object to analyse.} +} +\value{ +The object's peak value, as a numeric scalar. +} +\description{ +Gets the peak value of an object. +} diff --git a/man/pitch_commonality.Rd b/man/pitch_commonality.Rd new file mode 100644 index 0000000..9556bd6 --- /dev/null +++ b/man/pitch_commonality.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{pitch_commonality} +\alias{pitch_commonality} +\title{Get pitch commonality} +\usage{ +pitch_commonality(x, y, ...) +} +\arguments{ +\item{x}{The first sonority to compare, passed to \code{\link{pitch_salience}()}. +Typically will be a numeric vector of MIDI pitches.} + +\item{y}{The second sonority to compare, passed to \code{\link{pitch_salience}()}. +Typically will be a numeric vector of MIDI pitches.} + +\item{...}{Further arguments to pass to \code{\link{pitch_salience}()}.} +} +\value{ +Pitch commonality, as a numeric scalar. +} +\description{ +Gets the pitch commonality between two sonorities, after +\insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/pitch_distance.Rd b/man/pitch_distance.Rd new file mode 100644 index 0000000..52cec7b --- /dev/null +++ b/man/pitch_distance.Rd @@ -0,0 +1,27 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{pitch_distance} +\alias{pitch_distance} +\title{Get pitch distance} +\usage{ +pitch_distance(x, y, ...) +} +\arguments{ +\item{x}{The first sonority to compare, passed to \code{\link{pitch_salience}()}. +Typically will be a numeric vector of MIDI pitches.} + +\item{y}{The second sonority to compare, passed to \code{\link{pitch_salience}()}. +Typically will be a numeric vector of MIDI pitches.} + +\item{...}{Further arguments to pass to \code{\link{pitch_salience}()}.} +} +\value{ +Pitch distance, as a numeric scalar. +} +\description{ +Gets the pitch distance between two sonorities, after +\insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/pitch_salience.Rd b/man/pitch_salience.Rd new file mode 100644 index 0000000..345dd1d --- /dev/null +++ b/man/pitch_salience.Rd @@ -0,0 +1,36 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{pitch_salience} +\alias{pitch_salience} +\alias{pitch_salience.default} +\alias{pitch_salience.pitch_salience} +\alias{pitch_salience.parn94} +\title{Get pitch salience} +\usage{ +pitch_salience(x, ...) + +\method{pitch_salience}{default}(x, ...) + +\method{pitch_salience}{pitch_salience}(x, ...) + +\method{pitch_salience}{parn94}(x, ...) +} +\arguments{ +\item{x}{Object to analyse, passed to \code{\link{parn94}()}.} + +\item{...}{Further arguments to pass to \code{\link{parn94}()}.} +} +\value{ +Returns a vector where each element describes +the salience of a different chromatic pitch. +The first element of this vector corresponds to the +\code{min_midi} argument from \code{\link{parn94_params}}, +and the last element corresponds to the \code{max_midi} argument. +} +\description{ +Analyses the pitch salience of a sonority, after +\insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/popular_1_pc_chord_type.Rd b/man/popular_1_pc_chord_type.Rd new file mode 100644 index 0000000..64a7593 --- /dev/null +++ b/man/popular_1_pc_chord_type.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-corpdiss.R +\docType{data} +\name{popular_1_pc_chord_type} +\alias{popular_1_pc_chord_type} +\title{Consonance tables for popular music} +\description{ +This table summarises chord prevalences in the +McGill Billboard corpus \insertCite{Burgoyne2011}{hcorp}. +These pieces were sampled from the Billboard magazine's +United States "Hot 100" chart between 1958 and 1991, +and transcribed by expert musicians. +See \code{\link[hcorp]{popular_1}} for more details. +} +\details{ +Chords are represented as pitch-class chord types: +see \code{\link[hrep]{pc_chord_type}} for details. +} +\references{ +\insertAllCited{} +} +\keyword{data} diff --git a/man/pure_sonor.Rd b/man/pure_sonor.Rd new file mode 100644 index 0000000..0a90429 --- /dev/null +++ b/man/pure_sonor.Rd @@ -0,0 +1,31 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn94.R +\name{pure_sonor} +\alias{pure_sonor} +\alias{pure_sonor.parn94} +\alias{pure_sonor.default} +\title{Get pure sonorousness} +\usage{ +pure_sonor(x, k_p = parn94_params()$k_p, ...) + +\method{pure_sonor}{parn94}(x, k_p = parn94_params()$k_p, ...) + +\method{pure_sonor}{default}(x, k_p = parn94_params()$k_p, ...) +} +\arguments{ +\item{x}{Object to analyse.} + +\item{k_p}{Parncutt & Strasburger (1994) set this to 0.5 (p. 105).} + +\item{...}{Further parameters to pass to \code{\link{parn94}()}.} +} +\value{ +Pure sonorousness, a numeric scalar. +} +\description{ +Computes the pure sonorousness of a sound, after +\insertCite{Parncutt1994;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/rational_scale.Rd b/man/rational_scale.Rd new file mode 100644 index 0000000..52c8320 --- /dev/null +++ b/man/rational_scale.Rd @@ -0,0 +1,23 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-bowl18.R +\docType{data} +\name{rational_scale} +\alias{rational_scale} +\title{Rational scale} +\format{ +An object of class \code{matrix} (inherits from \code{array}) with 2 rows and 12 columns. +} +\usage{ +rational_scale +} +\description{ +This defines the rational scale used when computing harmonicity with +\code{\link{gill09_harmonicity}}. +It is a matrix with 2 rows and 12 columns, +where the first row corresponds to fraction numerators, +and the second row corresponds to fraction denominators. +Column i identifes the interval of size (i - 1) semitones. +For example, column 8 identifies the perfect fifth +(7 semitones) as a 3:2 ratio. +} +\keyword{data} diff --git a/man/root.Rd b/man/root.Rd new file mode 100644 index 0000000..20cd454 --- /dev/null +++ b/man/root.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn88.R +\name{root} +\alias{root} +\title{Root} +\usage{ +root(...) +} +\arguments{ +\item{...}{Arguments to pass to \code{\link{incon}}.} +} +\value{ +The estimated chord root (integer scalar). +} +\description{ +Estimates the chord root of a pitch-class set using the root-finding model of +\insertCite{Parncutt1988;textual}{incon}. +This function is a wrapper for \code{\link{incon}}. +} +\references{ +\insertAllCited{} +} diff --git a/man/root_ambiguity.Rd b/man/root_ambiguity.Rd new file mode 100644 index 0000000..2478514 --- /dev/null +++ b/man/root_ambiguity.Rd @@ -0,0 +1,22 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn88.R +\name{root_ambiguity} +\alias{root_ambiguity} +\title{Root ambiguity} +\usage{ +root_ambiguity(...) +} +\arguments{ +\item{...}{Arguments to pass to \code{\link{parn88}}.} +} +\value{ +The root ambiguity (numeric scalar). +} +\description{ +Estimates the root ambiguity of a pitch-class set using the root-finding model of +\insertCite{Parncutt1988;textual}{incon}. +This function is a wrapper for \code{\link{parn88}}. +} +\references{ +\insertAllCited{} +} diff --git a/man/root_by_pc_chord.Rd b/man/root_by_pc_chord.Rd new file mode 100644 index 0000000..700ec36 --- /dev/null +++ b/man/root_by_pc_chord.Rd @@ -0,0 +1,13 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn88.R +\docType{data} +\name{root_by_pc_chord} +\alias{root_by_pc_chord} +\title{Root by pitch-class chord} +\description{ +This vector stores precomputed chord roots for +all pitch-class chords, +indexed by their encoding as defined in the "hrep" package. +See \code{\link[hrep]{pc_chord}} for more details. +} +\keyword{data} diff --git a/man/root_support_weights.Rd b/man/root_support_weights.Rd new file mode 100644 index 0000000..ed227a4 --- /dev/null +++ b/man/root_support_weights.Rd @@ -0,0 +1,21 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-parn88.R +\docType{data} +\name{root_support_weights} +\alias{root_support_weights} +\title{Root support weights} +\format{ +An object of class \code{list} of length 2. +} +\usage{ +root_support_weights +} +\description{ +A list of different root support weights that may be used +by the root-finding algorithm of \insertCite{Parncutt1988;textual}{incon}. +See \code{\link{parn88}} for more information. +} +\references{ +\insertAllCited{} +} +\keyword{datasets} diff --git a/man/roughness_hutch.Rd b/man/roughness_hutch.Rd new file mode 100644 index 0000000..57c269a --- /dev/null +++ b/man/roughness_hutch.Rd @@ -0,0 +1,69 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{roughness_hutch} +\alias{roughness_hutch} +\alias{roughness_hutch.default} +\alias{roughness_hutch.sparse_fr_spectrum} +\title{Spectral roughness (Hutchinson & Knopoff)} +\usage{ +roughness_hutch( + x, + cbw_cut_off = 1.2, + a = 0.25, + b = 2, + dissonance_function = hutch_dissonance_function, + ... +) + +\method{roughness_hutch}{default}( + x, + cbw_cut_off = 1.2, + a = 0.25, + b = 2, + dissonance_function = hutch_dissonance_function, + ... +) + +\method{roughness_hutch}{sparse_fr_spectrum}( + x, + cbw_cut_off = 1.2, + a = 0.25, + b = 2, + dissonance_function = hutch_dissonance_function, + ... +) +} +\arguments{ +\item{x}{Object to analyse, which is coerced to the class +\code{\link[hrep]{sparse_fr_spectrum}}. +\itemize{ +\item Numeric vectors will be treated as vectors of MIDI note numbers, +and expanded into their implied harmonics. +\item Two-element lists will be treated as finalised spectra, +with the first element being a numeric vector of frequencies, +and the second element being a numeric vector of amplitudes. +}} + +\item{cbw_cut_off}{Parameter passed to \code{\link{hutch_g}()}.} + +\item{a}{Parameter passed to \code{\link{hutch_g}()}.} + +\item{b}{Parameter passed to \code{\link{hutch_g}()}.} + +\item{dissonance_function}{Function for computing dissonance contribution as a function of +critical bandwidth distance, defaulting to \code{\link{hutch_dissonance_function}}. +Custom functions may be specified here as long as they use the same parameter list +as the original function.} + +\item{...}{Further arguments to pass to \code{\link[hrep]{sparse_fr_spectrum}}.} +} +\value{ +Numeric scalar, identifying the roughness of the spectrum. +} +\description{ +Gets the roughness of a sonority according to the model of +\insertCite{Hutchinson1978;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/roughness_seth.Rd b/man/roughness_seth.Rd new file mode 100644 index 0000000..f863e17 --- /dev/null +++ b/man/roughness_seth.Rd @@ -0,0 +1,55 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{roughness_seth} +\alias{roughness_seth} +\alias{roughness_seth.default} +\alias{roughness_seth.sparse_fr_spectrum} +\title{Spectral roughness (Sethares)} +\usage{ +roughness_seth(x, min_amplitude = TRUE, ...) + +\method{roughness_seth}{default}(x, min_amplitude = TRUE, ...) + +\method{roughness_seth}{sparse_fr_spectrum}(x, min_amplitude = TRUE, ...) +} +\arguments{ +\item{x}{Object to analyse, which is coerced to the class +\code{\link[hrep]{sparse_fr_spectrum}}. +\itemize{ +\item Numeric vectors will be treated as vectors of MIDI note numbers, +and expanded into their implied harmonics. +\item Two-element lists will be treated as finalised spectra, +with the first element being a numeric vector of frequencies, +and the second element being a numeric vector of amplitudes. +}} + +\item{min_amplitude}{See \code{\link{dyad_roughness_seth}}.} + +\item{...}{Further arguments to pass to \code{\link[hrep]{sparse_fr_spectrum}}.} +} +\value{ +Estimated roughness, as a numeric scalar. +} +\description{ +Gets the roughness of a sonority according to the model of Sethares (1993). +By default, the algorithm is modified according to +\insertCite{Sethares2005;textual}{incon} and +\insertCite{Weisser2013;textual}{incon}: +roughness is proportional to the minimum amplitude of each pair of partials, +not the product of their amplitudes. +This behaviour can be disabled by setting \code{min_amplitude = FALSE}. +} +\note{ +\insertCite{Sethares2005;textual}{incon} +suggests using loudnesses instead of amplitudes. +However, he acknowledges that loudness is difficult to calculate +for arbitrary timbres. +Furthermore, if we replace amplitude with roughness, +we lose the original model's invariance to multiplicative +scaling of the original signal. +In this implementation, we therefore stay with amplitude, +consistent with \insertCite{Sethares1993;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/roughness_vass.Rd b/man/roughness_vass.Rd new file mode 100644 index 0000000..379eff2 --- /dev/null +++ b/man/roughness_vass.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-dycon.R +\name{roughness_vass} +\alias{roughness_vass} +\alias{roughness_vass.default} +\alias{roughness_vass.sparse_fr_spectrum} +\title{Spectral roughness (Vassilakis)} +\usage{ +roughness_vass(x, ...) + +\method{roughness_vass}{default}(x, ...) + +\method{roughness_vass}{sparse_fr_spectrum}(x, ...) +} +\arguments{ +\item{x}{Object to analyse, which is coerced to the class +\code{\link[hrep]{sparse_fr_spectrum}}. +\itemize{ +\item Numeric vectors will be treated as vectors of MIDI note numbers, +and expanded into their implied harmonics. +\item Two-element lists will be treated as finalised spectra, +with the first element being a numeric vector of frequencies, +and the second element being a numeric vector of amplitudes. +}} + +\item{...}{Further arguments to pass to \code{\link[hrep]{sparse_fr_spectrum}}.} +} +\value{ +Estimated roughness, as a numeric scalar. +} +\description{ +Gets the roughness of a sonority according to the model of +\insertCite{Vassilakis2001;textual}{incon} +\insertCite{Villegas2010;textual}{incon} +} +\references{ +\insertAllCited{} +} diff --git a/man/roughness_wang.Rd b/man/roughness_wang.Rd new file mode 100644 index 0000000..3f5305c --- /dev/null +++ b/man/roughness_wang.Rd @@ -0,0 +1,95 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-wang13.R +\name{roughness_wang} +\alias{roughness_wang} +\alias{roughness_wang.default} +\alias{roughness_wang.sparse_fr_spectrum} +\title{Wang et al.'s (2013) roughness model} +\usage{ +roughness_wang( + x, + detail = FALSE, + include_phase_impact_factors = FALSE, + unit_amplitude_in_dB = 60, + msg = function(n, N, msg) if (interactive()) message(n, "/", N, ": ", msg), + ... +) + +\method{roughness_wang}{default}( + x, + detail = FALSE, + include_phase_impact_factors = FALSE, + unit_amplitude_in_dB = 60, + msg = function(n, N, msg) if (interactive()) message(n, "/", N, ": ", msg), + ... +) + +\method{roughness_wang}{sparse_fr_spectrum}( + x, + detail = FALSE, + include_phase_impact_factors = FALSE, + unit_amplitude_in_dB = 60, + msg = function(n, N, msg) if (interactive()) message(n, "/", N, ": ", msg), + ... +) +} +\arguments{ +\item{x}{Object to analyse, +which will be coerced to an object of class +\code{\link[hrep]{sparse_fr_spectrum}}. +Various input types are possible: +\itemize{ +\item Numeric vectors will be treated as vectors of MIDI note numbers, +which will be expanded into their implied harmonics. +\item A two-element list can be used to define a harmonic spectrum. +The first element should be a vector of frequencies in Hz, +the second a vector of amplitudes. +\item The function also accepts classes from the \code{hrep} package, +such as produced by \code{\link[hrep]{pi_chord}()} and +\code{\link[hrep]{sparse_fr_spectrum}()}. +}} + +\item{detail}{(Logical scalar) Whether to return detailed output information.} + +\item{include_phase_impact_factors}{(Logical scalar) +Whether to include phase impact factors in roughness computation. +Set to \code{TRUE} to recover the original specifications of Wang et al. (2013). +However, disabling this feature (by leaving the parameter at \code{FALSE}) +seems to result in better estimation of perceptual consonance.} + +\item{unit_amplitude_in_dB}{(Numeric scalar) +Determines the decibel level of a partial with amplitude 1. +When the input is a musical chord, +this will correspond to the decibel level of the fundamental frequencies +of each chord tone.} + +\item{msg}{Function to be called to give progress updates. +This function should accept three arguments: +\code{n}, an integer identifying the current position in the pipeline, +\code{N}, an integer identifying the length of the pipeline, +and \code{msg}, a string providing a longer-format description +of the current position in the pipeline. +Pass \code{NULL} to disable progress updates.} + +\item{...}{Additional parameters to pass to +\code{\link[hrep]{sparse_fr_spectrum}}. +\itemize{ +\item \code{num_harmonics}: Number of harmonics to use when expanding +chord tones into their implied harmonics. +\item \code{roll_off}: Rate of amplitude roll-off for the harmonics. +}} +} +\value{ +If \code{detail == FALSE}, a numeric vector of roughnesses, +otherwise a list containing detailed algorithm output. +} +\description{ +Gets the roughness of a sonority according to the model of Wang et al. (2013). +} +\note{ +This implementation is designed for sparse input spectra, that is, +spectra containing only a few (< 100) components. +} +\references{ +\insertRef{Wang2013}{incon} +} diff --git a/man/smooth_log_periodicity.Rd b/man/smooth_log_periodicity.Rd new file mode 100644 index 0000000..4125abd --- /dev/null +++ b/man/smooth_log_periodicity.Rd @@ -0,0 +1,38 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-stolz15.R +\name{smooth_log_periodicity} +\alias{smooth_log_periodicity} +\alias{smooth_log_periodicity.default} +\alias{smooth_log_periodicity.pi_chord} +\title{Smoothed log periodicity} +\usage{ +smooth_log_periodicity(x, d = 0.011) + +\method{smooth_log_periodicity}{default}(x, d = 0.011) + +\method{smooth_log_periodicity}{pi_chord}(x, d = 0.011) +} +\arguments{ +\item{x}{Sonority to analyse. +This will be coerced to an object of class \code{\link[hrep]{pi_chord}}. +Numeric inputs will be interpreted as MIDI note numbers.} + +\item{d}{(numeric scalar): +Maximal allowed error in the algorithm's +interval approximation step, expressed as +a fraction of the original interval. +The default value, 0.011, corresponds to 'Rational Tuning II' +in Stolzenburg's paper.} +} +\value{ +A numeric scalar identifying the chord's periodicity. +High values mean a higher period length, lower periodicity, +and lower consonance. +} +\description{ +This function computes a chord's smoothed logarithmic periodicity, +after \insertCite{Stolzenburg2015;textual}{incon}. +} +\references{ +\insertAllCited{} +} diff --git a/man/sweep_harmonic_template.Rd b/man/sweep_harmonic_template.Rd new file mode 100644 index 0000000..c90b150 --- /dev/null +++ b/man/sweep_harmonic_template.Rd @@ -0,0 +1,49 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-har18.R +\name{sweep_harmonic_template} +\alias{sweep_harmonic_template} +\alias{sweep_harmonic_template.pc_set} +\alias{sweep_harmonic_template.milne_pc_spectrum} +\title{Sweep harmonic template} +\usage{ +sweep_harmonic_template( + x, + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + array_dim = 1200, + ... +) + +\method{sweep_harmonic_template}{pc_set}( + x, + num_harmonics = 12, + rho = 0.75, + sigma = 6.83, + array_dim = 1200, + ... +) + +\method{sweep_harmonic_template}{milne_pc_spectrum}(x, num_harmonics = 12, rho = 0.75, sigma = 6.83, ...) +} +\arguments{ +\item{x}{Object to analyse.} + +\item{num_harmonics}{See \code{\link{pc_harmonicity}}.} + +\item{rho}{See \code{\link{pc_harmonicity}}.} + +\item{sigma}{See \code{\link{pc_harmonicity}}.} + +\item{array_dim}{See \code{\link{pc_harmonicity}}.} + +\item{...}{Arguments passed to specific methods.} +} +\value{ +An object of class \code{\link[hrep]{milne_pc_spectrum}}, +identifying each pitch class with a perceptual weight +corresponding to its harmonic template fit. +} +\description{ +Sweeps a harmonic template over an input spectrum. +} diff --git a/man/sweep_template.Rd b/man/sweep_template.Rd new file mode 100644 index 0000000..6b15725 --- /dev/null +++ b/man/sweep_template.Rd @@ -0,0 +1,25 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-har18.R +\name{sweep_template} +\alias{sweep_template} +\title{Sweep template} +\usage{ +sweep_template(x, template, legacy = FALSE) +} +\arguments{ +\item{x}{(Numeric vector) +The vector to be swept over.} + +\item{template}{(Numeric vector) +The template to sweep over \code{x}. +Should have the same dimensionality as \code{x}.} + +\item{legacy}{(Logical scalar) +Whether to use the legacy R implementation +(default = \code{FALSE}). +Otherwise the faster C++ implementation is used.} +} +\description{ +Sweeps a circular template over a circular vector +and computes the cosine similarity at each possible offset. +} diff --git a/man/type.Rd b/man/type.Rd new file mode 100644 index 0000000..9723cfa --- /dev/null +++ b/man/type.Rd @@ -0,0 +1,17 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/model-corpdiss.R +\name{type} +\alias{type} +\title{Get type} +\usage{ +type(x) +} +\arguments{ +\item{x}{Object.} +} +\value{ +(Character scalar) Type. +} +\description{ +Gets the type of an object. +} diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..22034c4 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,3 @@ +*.o +*.so +*.dll diff --git a/src/RcppExports.cpp b/src/RcppExports.cpp new file mode 100644 index 0000000..c33a35a --- /dev/null +++ b/src/RcppExports.cpp @@ -0,0 +1,75 @@ +// Generated by using Rcpp::compileAttributes() -> do not edit by hand +// Generator token: 10BE3573-1514-4C36-9D1C-5A225CD40393 + +#include + +using namespace Rcpp; + +#ifdef RCPP_USE_GLOBAL_ROSTREAM +Rcpp::Rostream& Rcpp::Rcout = Rcpp::Rcpp_cout_get(); +Rcpp::Rostream& Rcpp::Rcerr = Rcpp::Rcpp_cerr_get(); +#endif + +// mod +double mod(double x, int base); +RcppExport SEXP _incon_mod(SEXP xSEXP, SEXP baseSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< double >::type x(xSEXP); + Rcpp::traits::input_parameter< int >::type base(baseSEXP); + rcpp_result_gen = Rcpp::wrap(mod(x, base)); + return rcpp_result_gen; +END_RCPP +} +// get_index +double get_index(NumericVector& x, int index, int offset); +RcppExport SEXP _incon_get_index(SEXP xSEXP, SEXP indexSEXP, SEXP offsetSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< NumericVector& >::type x(xSEXP); + Rcpp::traits::input_parameter< int >::type index(indexSEXP); + Rcpp::traits::input_parameter< int >::type offset(offsetSEXP); + rcpp_result_gen = Rcpp::wrap(get_index(x, index, offset)); + return rcpp_result_gen; +END_RCPP +} +// cosine_similarity_cpp +double cosine_similarity_cpp(NumericVector& x, NumericVector& template_, int offset); +RcppExport SEXP _incon_cosine_similarity_cpp(SEXP xSEXP, SEXP template_SEXP, SEXP offsetSEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< NumericVector& >::type x(xSEXP); + Rcpp::traits::input_parameter< NumericVector& >::type template_(template_SEXP); + Rcpp::traits::input_parameter< int >::type offset(offsetSEXP); + rcpp_result_gen = Rcpp::wrap(cosine_similarity_cpp(x, template_, offset)); + return rcpp_result_gen; +END_RCPP +} +// sweep_template_cpp +NumericVector sweep_template_cpp(NumericVector& x, NumericVector& template_); +RcppExport SEXP _incon_sweep_template_cpp(SEXP xSEXP, SEXP template_SEXP) { +BEGIN_RCPP + Rcpp::RObject rcpp_result_gen; + Rcpp::RNGScope rcpp_rngScope_gen; + Rcpp::traits::input_parameter< NumericVector& >::type x(xSEXP); + Rcpp::traits::input_parameter< NumericVector& >::type template_(template_SEXP); + rcpp_result_gen = Rcpp::wrap(sweep_template_cpp(x, template_)); + return rcpp_result_gen; +END_RCPP +} + +static const R_CallMethodDef CallEntries[] = { + {"_incon_mod", (DL_FUNC) &_incon_mod, 2}, + {"_incon_get_index", (DL_FUNC) &_incon_get_index, 3}, + {"_incon_cosine_similarity_cpp", (DL_FUNC) &_incon_cosine_similarity_cpp, 3}, + {"_incon_sweep_template_cpp", (DL_FUNC) &_incon_sweep_template_cpp, 2}, + {NULL, NULL, 0} +}; + +RcppExport void R_init_incon(DllInfo *dll) { + R_registerRoutines(dll, NULL, CallEntries, NULL, NULL); + R_useDynamicSymbols(dll, FALSE); +} diff --git a/src/code.cpp b/src/code.cpp new file mode 100644 index 0000000..38fb6dd --- /dev/null +++ b/src/code.cpp @@ -0,0 +1,61 @@ +#include +using namespace Rcpp; + +// [[Rcpp::export]] +double mod(double x, int base) { + return x - floor(x / base) * base; +} + +// [[Rcpp::export]] +double get_index( + NumericVector &x, + int index, + int offset +) { + int N = x.size(); + int j = (index + offset) % N; + return x[j]; +} + +// [[Rcpp::export]] +double cosine_similarity_cpp( + NumericVector &x, + NumericVector &template_, + int offset +) { + double Sxy = 0.0; + double Sxx = 0.0; + double Syy = 0.0; + + int N = x.size(); + int N2 = template_.size(); + + if (N != N2) { + stop(" and should both have the same size."); + } + + for (int i = 0; i < N; i ++) { + double x_i = get_index(x, i, offset); + double y_i = template_[i]; + Sxy += x_i * y_i; + Sxx += x_i * x_i; + Syy += y_i * y_i; + } + + return Sxy / (sqrt(Sxx) * sqrt(Syy)); +} + +// [[Rcpp::export]] +NumericVector sweep_template_cpp( + NumericVector &x, + NumericVector &template_ +) { + int array_dim = x.size(); + NumericVector res = NumericVector(array_dim); + + for (int offset = 0; offset < array_dim; offset ++) { + res[offset] = cosine_similarity_cpp(x, template_, offset); + } + + return res; +} diff --git a/tests/testthat/test-bowl18.R b/tests/testthat/test-bowl18.R new file mode 100644 index 0000000..f4395a1 --- /dev/null +++ b/tests/testthat/test-bowl18.R @@ -0,0 +1,164 @@ +context("test-gill09_harmonicity") + +test_that("regression tests: dyads", { + df <- read.csv(system.file("bowling-data/dyads.csv", package = "incon"), + stringsAsFactors = FALSE) + chords <- purrr::pmap(list(df$pc_1, df$pc_2), ~ hrep::pi_chord(c(..1, ..2))) + + for (i in seq_along(chords)) { + expect_equal(df$bowling_harm_sim[i], + gill09_harmonicity(chords[[i]]), + tolerance = 1e-3) + } +}) + +test_that("regression tests: triads", { + df <- read.csv(system.file("bowling-data/triads.csv", package = "incon"), + stringsAsFactors = FALSE) + chords <- purrr::pmap(list(df$pc_1, df$pc_2, df$pc_3), + ~ hrep::pi_chord(c(..1, ..2, ..3))) + + set.seed(1) + ind <- sample(length(chords), size = 20L) + for (i in ind) { + expect_equal(df$bowling_harm_sim[i], + gill09_harmonicity(chords[[i]]), + tolerance = 1e-3) + } +}) + +test_that("regression tests: tetrads", { + df <- read.csv(system.file("bowling-data/tetrads.csv", package = "incon"), + stringsAsFactors = FALSE) + chords <- purrr::pmap(list(df$pc_1, df$pc_2, df$pc_3, df$pc_4), + ~ hrep::pi_chord(c(..1, ..2, ..3, ..4))) + + set.seed(1) + ind <- sample(length(chords), size = 20L) + for (i in ind) { + expect_equal(df$bowling_harm_sim[i], + gill09_harmonicity(chords[[i]]), + tolerance = 1e-3) + } +}) + +context("test-bowl18_min_freq_dist") + +test_that("regression tests: dyads", { + df <- read.csv(system.file("bowling-data/dyads.csv", package = "incon"), + stringsAsFactors = FALSE) + fr <- purrr::pmap(list(df$f_1, df$f_2), + ~ hrep::fr_chord(c(..1, ..2))) + min_fr_int_old <- df$bowling_min_freq_int + min_fr_int_new <- purrr::map_dbl(fr, bowl18_min_freq_dist) + + for (i in seq_along(fr)) { + expect_equal(min_fr_int_old[i], + min_fr_int_new[i]) + } +}) + +test_that("regression tests: triads", { + df <- read.csv(system.file("bowling-data/triads.csv", package = "incon"), + stringsAsFactors = FALSE) + fr <- purrr::pmap(list(df$f_1, df$f_2, df$f_3), + ~ hrep::fr_chord(c(..1, ..2, ..3))) + min_fr_int_old <- df$bowling_min_freq_int + min_fr_int_new <- purrr::map_dbl(fr, bowl18_min_freq_dist) + + for (i in seq_along(fr)) { + expect_equal(min_fr_int_old[i], + min_fr_int_new[i]) + } +}) + +test_that("regression tests: tetrads", { + df <- read.csv(system.file("bowling-data/tetrads.csv", package = "incon"), + stringsAsFactors = FALSE) + fr <- purrr::pmap(list(df$f_1, df$f_2, df$f_3, df$f_4), + ~ hrep::fr_chord(c(..1, ..2, ..3, ..4))) + min_fr_int_old <- df$bowling_min_freq_int + min_fr_int_new <- purrr::map_dbl(fr, bowl18_min_freq_dist) + + for (i in seq_along(fr)) { + expect_equal(min_fr_int_old[i], + min_fr_int_new[i]) + } +}) + +context("test-gcd") + +test_that("examples", { + x <- rational_chord(matrix(c(4, 5, 6, + 1, 1, 1), + nrow = 2, byrow = TRUE)) + expect_equal(gcd(x), fraction(c(1, 1))) + + x <- rational_chord(matrix(c(3, 4, 5, + 2, 3, 4), + nrow = 2, byrow = TRUE)) + + expect_equal(gcd(x), fraction(c(1, 12))) +}) + +context("test-rationalise_chord") + +test_that("examples", { + expect_equal( + rationalise_chord(hrep::pi_chord(c(0, 4, 7)), tonic = 0), + rational_chord(matrix(c(1, 5, 3, + 1, 4, 2), + nrow = 2, byrow = TRUE)) + ) + + expect_equal( + rationalise_chord(hrep::pi_chord(c(60, 64, 67)), tonic = 0), + rational_chord(matrix(c(1, 5, 3, + 1, 4, 2), + nrow = 2, byrow = TRUE)) + ) + + expect_equal( + rationalise_chord(hrep::pi_chord(c(60, 64, 67 + 12)), tonic = 0), + rational_chord(matrix(c(1, 5, 3, + 1, 4, 1), + nrow = 2, byrow = TRUE)) + ) +}) + +context("test-rational_pitch_class") + +test_that("examples", { + expect_equal(rationalise_pitch_class(0), fraction(c(1, 1))) + expect_equal(rationalise_pitch_class(7), fraction(c(3, 2))) +}) + +context("test-rational_pitch") + +test_that("examples", { + expect_equal(rationalise_pitch(7), fraction(c(3, 2))) + expect_equal(rationalise_pitch(0), fraction(c(1, 1))) + expect_equal(rationalise_pitch(12), fraction(c(2, 1))) + expect_equal(rationalise_pitch(12 + 7), fraction(c(3, 1))) + expect_equal(rationalise_pitch(12 + 6), fraction(c(14, 5))) +}) + +context("test-tonic") + +test_that("examples", { + expect_gt(gill09_harmonicity(c(0, 4, 7) + 0), + gill09_harmonicity(c(0, 4, 7) + 2)) + + expect_equal(gill09_harmonicity(c(0, 4, 7) + 0), + gill09_harmonicity(c(0, 4, 7) + 2, tonic = 2)) + + for (i in seq_len(10)) { + tonic <- sample(11, 1) + x1 <- sample(40, size = 3) %>% hrep::pi_chord() + x2 <- x1 + tonic + expect_equal( + gill09_harmonicity(x1), + gill09_harmonicity(x2, tonic = tonic) + ) + } +}) diff --git a/tests/testthat/test-corpdiss.R b/tests/testthat/test-corpdiss.R new file mode 100644 index 0000000..0818e6d --- /dev/null +++ b/tests/testthat/test-corpdiss.R @@ -0,0 +1,50 @@ +context("test-corpus_dissonance_table") + +library(magrittr) + +test_that("examples", { + list( + c(1, 2), + c(3, 4), + c(5, 6) + ) %>% + purrr::map(~ hrep::coded_vec(., type = "pc_set")) %>% + hrep::corpus(type = "pc_set") %>% + corpus_dissonance_table(type = "pc_set") %>% + expect_equal( + .corpus_dissonance_table( + tibble::tibble( + count = c(rep(1, times = 6), rep(0, times = hrep::alphabet_size("pc_set") - 6)), + prob = (count + 1) / sum(count + 1), + neg_log_prob = - log(prob) + ), type = "pc_set")) +}) + + +context("test-corpus_dissonance") + +test_that("examples", { + test <- function(x, y, tolerance = 1e-5) { + expect_equal(corpus_dissonance(x), y, tolerance = tolerance) + } + test(c(60, 64, 67), 0.7802433) + test(c(64, 67, 72), 4.06715) + test(c(60, 63, 67), 2.271055) + test(c(60, 63, 66), 6.134397) + test(c(60, 64, 68), 7.551463) + test(c(60, 64, 67, 70), 2.610535) + test(c(60, 63, 67, 70), 2.650456) + + expect_equal( + corpus_dissonance(c(60, 64, 67)), + corpus_dissonance(hrep::pc_chord(c(0, 4, 7))) + ) + expect_equal( + corpus_dissonance(c(64, 67, 72)), + corpus_dissonance(hrep::pc_chord(c(4, 7, 0))) + ) + expect_equal( + corpus_dissonance(hrep::pc_set(c(0, 3, 6))), + corpus_dissonance(hrep::pc_chord_type(c(0, 3, 6))) + ) +}) diff --git a/tests/testthat/test-dycon.R b/tests/testthat/test-dycon.R new file mode 100644 index 0000000..53cdb6b --- /dev/null +++ b/tests/testthat/test-dycon.R @@ -0,0 +1,172 @@ +context("hutch") + +library(magrittr) + +test_that( + "hutch_g", { + expect_equal( + hutch_g( + 1, cbw_cut_off = 1.2 + ) %>% round(digits = 4), + 0.0397 + ) + expect_equal( + hutch_g( + 0.5, cbw_cut_off = 1.2 + ) %>% round(digits = 4), + 0.5413 + ) + expect_equal( + hutch_g( + 1.5, cbw_cut_off = NULL + ) %>% round(digits = 4), + 0.0016 + ) + expect_equal( + hutch_g( + 1.5, cbw_cut_off = 1.2 + ) %>% round(digits = 4), + 0 + ) + } +) + +test_that( + "hutch_cbw", { + expect_equal( + hutch_cbw(400, 440) %>% round(digits = 3), + 87.225 + ) + expect_equal( + hutch_cbw(400, 380) %>% round(digits = 3), + 83.123 + ) + } +) + +test_that("get_roughness_hutch", { + test_midi <- function(midi, expect, num_harmonics, tolerance = 1e-3) { + midi %>% + roughness_hutch(num_harmonics = num_harmonics) %>% + expect_equal(expect, tolerance = tolerance) + } + test_midi("60 61", 0.499, num_harmonics = 1) + test_midi("69 70", 0.491, num_harmonics = 1) + test_midi("60 61", 0.484, num_harmonics = 11) + test_midi("60 64 67", 0.120, num_harmonics = 11) + test_midi("60 63 67", 0.130, num_harmonics = 11) + + expect_equal( + roughness_hutch(c(60, 61), dissonance_function = function(...) 0), + 0 + ) +}) + +context("test-sethares") + +test_that("Regression tests generated from Sethares's implementation", { + # See below for MATLAB implementation of Sethares's (1993) model, + # sourced from http://sethares.engr.wisc.edu/comprog.html. + # To reproduce the exact results, it would be necessary + # to adjust the parameters slightly: s1 = 0.0207, s2 = 18.96, a = 3.51 + # Note that Sethares's implementation also introduces an arbitrary + # scaling factor, which we need to compensate for in our testing. + f <- function(frequency, amplitude, ref = TRUE) { + x <- roughness_seth(hrep::sparse_fr_spectrum(list(frequency, amplitude))) + if (ref) { + x / f(frequency = c(440, 460), amplitude = c(1, 1), ref = FALSE) + } else x + } + + # MATLAB: + # dissmeasure([440, 460, 480], [1, 1, 1]) / dissmeasure([440, 460], [1, 1]) + expect_equal(f(c(440, 460, 480), c(1, 1, 1)), 2.9194, tolerance = 1e-2) + + expect_equal(f(c(440, 460, 480), c(1, 2, 3)), 3.9161, tolerance = 1e-2) + expect_equal(f(c(440, 460, 480), c(3, 2, 1)), 3.9194, tolerance = 1e-2) + expect_equal(f(c(300, 250, 275, 425), c(1.5, 2, 9, 4)), 4.8657, tolerance = 1e-2) +}) + +# Sethares's MATLAB code: +# http://sethares.engr.wisc.edu/comprog.html + +# function d=dissmeasure(fvec,amp) +# % +# % given a set of partials in fvec, +# % with amplitudes in amp, +# % this routine calculates the dissonance +# % +# Dstar=0.24; S1=0.0207; S2=18.96; C1=5; C2=-5; +# A1=-3.51; A2=-5.75; firstpass=1; +# N=length(fvec); +# [fvec,ind]=sort(fvec); +# ams=amp(ind); +# D=0; +# for i=2:N +# Fmin=fvec(1:N-i+1); +# S=Dstar./(S1*Fmin+S2); +# Fdif=fvec(i:N)-fvec(1:N-i+1); +# a=min(ams(i:N),ams(1:N-i+1)); +# Dnew=a.*(C1*exp(A1*S.*Fdif)+C2*exp(A2*S.*Fdif)); +# D=D+Dnew*ones(size(Dnew))'; +# end +# d=D; + +context("test-vass") + +library(magrittr) +library(tibble) + +test_that("Comparing model outputs to Vassilakis (2001, p. 210)", { + # This table comes from p. 208 + .f <- tribble( + ~ f1, ~ f2, ~ f3, ~ f4, ~ f5, ~ f6, + 262, 526, 790, 1049, 1318, 1573, + 277, 554, 837, 1118, 1398, 1677, + 294, 590, 886, 1180, 1473, 1772, + 311, 624, 932, 1244, 1569, 1873, + 330, 663, 995, 1323, 1654, 1994, + 349, 701, 1053, 1408, 1751, 2107, + 370, 741, 1118, 1482, 1852, 2235, + 392, 783, 1179, 1570, 1973, 2373, + 415, 834, 1250, 1670, 2093, 2499, + 440, 884, 1329, 1768, 2200, 2666, + 466, 937, 1400, 1874, 2345, 2799, + 494, 990, 1484, 1985, 2476, 2973, + 524, 1052, 1573, 2110, 2634, 3154 + ) + .a <- 1 / 1:6 + get_tone <- function(pc) { + hrep::sparse_fr_spectrum(list(as.numeric(.f[pc + 1, ]), .a)) + } + get_dyad <- function(pc_1, pc_2) { + hrep::combine_sparse_spectra(get_tone(pc_1), + get_tone(pc_2)) + } + get_dyad_roughness <- function(pc_1, pc_2) { + get_dyad(pc_1, pc_2) %>% + {roughness_vass(list(hrep::freq(.), hrep::amp(.)))} + } + + # These results come from p. 210 + res <- tibble(int = 2:12, + old = 40.383 / + c(27.617, 18.117, 16.002, + 11.446, 12.826, 6.17877, + 10.103, 5.782, 6.214, + 6.996, 1.589)) + res$new <- vapply(res$int, function(x) { + get_dyad_roughness(0, 1) / get_dyad_roughness(0, x) + }, numeric(1)) + res + + expect_gt(cor(res$old, res$new), 0.998) +}) + +test_that("zero amplitudes", { + # The original version sometimes returned NaN when the spectrum contained + # amplitudes of magnitude zero + spectrum <- hrep:::.sparse_fr_spectrum(frequency = c(400, 420, 440, 600), + amplitude = c(1, 0, 0, 1)) + expect_false(is.na(roughness_vass(spectrum))) +}) diff --git a/tests/testthat/test-har18.R b/tests/testthat/test-har18.R new file mode 100644 index 0000000..916bd66 --- /dev/null +++ b/tests/testthat/test-har18.R @@ -0,0 +1,175 @@ +context("test-cpp") + +test_that("get_index", { + x <- c(0, 1, 2, 3) + + expect_equal(get_index(x, index = 0, offset = 0), 0) + expect_equal(get_index(x, index = 1, offset = 0), 1) + expect_equal(get_index(x, index = 2, offset = 0), 2) + expect_equal(get_index(x, index = 3, offset = 0), 3) + + expect_equal(get_index(x, index = 0, offset = 1), 1) + expect_equal(get_index(x, index = 1, offset = 1), 2) + expect_equal(get_index(x, index = 2, offset = 1), 3) + expect_equal(get_index(x, index = 3, offset = 1), 0) + + expect_equal(get_index(x, index = 0, offset = 2), 2) + expect_equal(get_index(x, index = 1, offset = 2), 3) + expect_equal(get_index(x, index = 2, offset = 2), 0) + expect_equal(get_index(x, index = 3, offset = 2), 1) +}) + +test_that("cosine_similarity", { + x <- c(12, 25, -40, 20, 49) + y <- c(-40, 30, 10, 5, -30) + + expect_equal( + cosine_similarity_cpp(x, y, offset = 0), # <-- C++ version + cosine_similarity(x, y), # <-- R version + ) + + expect_equal( + cosine_similarity_cpp(x, y, offset = 1), + cosine_similarity(x[c(2:5, 1)], y), + ) + + expect_equal( + cosine_similarity_cpp(x, y, offset = 2), + cosine_similarity(x[c(3:5, 1:2)], y), + ) + + expect_equal( + cosine_similarity_cpp(x, y, offset = 3), + cosine_similarity(x[c(4:5, 1:3)], y), + ) +}) + +test_that("sweep_template", { + x <- c(-5, 20, 15, 35, 40, 20) + y <- c(20, 15, 27, 40, 10, 15) + + expect_equal( + sweep_template(x, y, legacy = TRUE), + sweep_template_cpp(x, y) + ) +}) + +context("test-harmonicity") + +library(hrep) +library(magrittr) + +test_that("Legacy comparisons with HarmonyStats package", { + pc_set_ids <- c(1, 100, 300, 500, 650, 800, 900, 1000, 1200, 1500) + pc_sets <- pc_set_ids %>% coded_vec("pc_set") %>% decode %>% as.list + pc_set_sizes <- vapply(pc_sets, length, integer(1)) + + # The following commented out code was used to generate the + # reference vector. + + # library(tidyverse) + # x <- unclass(readRDS("/Users/peter/Dropbox/Academic/projects/pearce-marcus/harmony/HarmonyStats/inst/extdata/feature_cache.rds")) + # scale_info <- attr(x, "scale_info")$instantaneous %>% filter(measure == "harmonicity") + # + # ref <- tibble(pc_set_id = pc_set_ids, + # size = pc_set_sizes, + # scaled_harmonicity = x@data$`NA`["harmonicity", pc_set_ids], + # center = scale_info$center[size], + # scale = scale_info$scale[size], + # harmonicity = scaled_harmonicity * scale + center) %>% + # pull(harmonicity) + # dump("ref", file = "") + + ref <- + c(1.56025992760304, 0.763094086276816, 0.699233982570559, 0.525533347758732, + 0.649556486732275, 0.763094086276816, 0.774127828829956, 0.525533347758732, + 0.699233982570559, 0.552087654180455) + + expect_equal(ref, + pc_sets %>% vapply(pc_harmonicity, numeric(1))) +}) + +context("test-kl_div_from_uniform") + +library(hrep) +library(magrittr) + +test_that("example output", { + spec <- c(0.2, 0.4, 0.3, 0.1) %>% .milne_pc_spectrum() + unif <- 1 / length(spec) + d <- 0 + for (i in seq_along(spec)) { + d <- d + spec[i] * log(spec[i] / unif, base = 2) + } + expect_equal( + d, kl_div_from_uniform(spec) + ) +}) + +test_that("invariance to the number of bins", { + v1 <- c(0.2, 0.4, 0.3, 0.1) + v2 <- rep(v1, each = 2) + s1 <- .milne_pc_spectrum(v1) + s2 <- .milne_pc_spectrum(v2) + expect_equal( + kl_div_from_uniform(s1), + kl_div_from_uniform(s2) + ) +}) + +test_that("invariance to magnitude", { + v1 <- c(0.2, 0.4, 0.3, 0.1) + v2 <- v1 * 2 + s1 <- .milne_pc_spectrum(v1) + s2 <- .milne_pc_spectrum(v2) + expect_equal( + kl_div_from_uniform(s1), + kl_div_from_uniform(s2) + ) +}) + +test_that("Peak measure produces different patterns to KL measure", { + organised_sub_peaks <- c(0, 0, 3, 0, 0, 5, 0, 0, 0, 0) %>% .milne_pc_spectrum() + disorganised_sub_peaks <- c(1, 1, 1, 0, 0, 5, 0, 0, 0, 0) %>% .milne_pc_spectrum() + expect_equal( + peak(organised_sub_peaks), + peak(disorganised_sub_peaks) + ) + expect_gt( + kl_div_from_uniform(organised_sub_peaks), + kl_div_from_uniform(disorganised_sub_peaks) + ) +}) + +context("test-peak") + +library(hrep) +library(magrittr) + +test_that("example", { + c(0, 0, 3, 4, 1) %>% + .milne_pc_spectrum() %>% + peak %>% + expect_equal(4) +}) + +test_that("invariance to doubling the number of bins", { + for (i in 1:10) { + spec1 <- rnorm(10) %>% .milne_pc_spectrum() + spec2 <- rep(as.numeric(spec1), each = 2) %>% .milne_pc_spectrum() + expect_equal( + peak(spec1), + peak(spec2) + ) + } +}) + +test_that("more pointy distributions get higher peaks", { + x <- seq(from = -100, to = 100) + pointy <- dnorm(x, sd = 1) + less_pointy <- dnorm(x, sd = 4) + expect_gt( + peak(.milne_pc_spectrum(pointy)), + peak(.milne_pc_spectrum(less_pointy)) + ) +}) diff --git a/tests/testthat/test-jl12.R b/tests/testthat/test-jl12.R new file mode 100644 index 0000000..df385cd --- /dev/null +++ b/tests/testthat/test-jl12.R @@ -0,0 +1,86 @@ +context("main") + +library(magrittr) + +# test_that("combined model", { +# # Test against the original paper +# df <- c("johnson-laird-2012-data/figure-2.csv", +# "johnson-laird-2012-data/figure-3.csv") %>% +# system.file(package = "incon") %>% +# lapply(function(x) read.csv(x, stringsAsFactors = FALSE)) %>% +# do.call(rbind, .) +# df$midi <- I(lapply(strsplit(df$midi, " "), as.numeric)) +# df$pc_set <- I(lapply(df$midi, function(x) { +# stopifnot(all.equal(x, sort(x))) +# res <- sort(x %% 12) +# stopifnot(!anyDuplicated(res)) +# res +# })) +# df$rule_1 <- sapply(df$pc_set, jl_rule_1) +# df$rule_2 <- sapply(df$pc_set, jl_rule_2) +# df$rule_3 <- sapply(df$pc_set, jl_rule_3) +# df$dual_process_2 <- sapply(df$pc_set, jl_tonal_dissonance) +# # expect_equal(df$dual_process, df$dual_process_2) +# # Seems there are mistakes in the original paper. +# ## Fig 2: +# # Here are the implied classifications in the paper: +# # 1: consonant according to all rules +# # 2-9: consonant by R1, dissonant by rule 2, consonant by R3 +# # 10-15: consonant by R1, dissononant by R2, dissonant by R3 +# # 16: medium by R1, dissonant by R2, consonant by R3 +# # 17-18: medium by R1, dissonant by R2, consonant by R3 (?) same as previous category +# # 19: dissonant by all rules +# ## Here are some example problems with the paper's annotations: +# # chord 6 cannot be built from stacked thirds, whereas chord 7 can. +# # => chord 6 should be in a different category to chord 7 +# # chord 11 can be bult by stacking thirds, whereas chord 10 can't +# # => chord 11 should come in a different category to chord 10 +# # chord 16 is not in the same category as chords 17-18, yet they all +# # have the same properties: +# # - from minor scale (R1) +# # - neither contain a major triad (R2) +# # - each can be built from thirds +# ## Similar problems exist for the Figure 3. +# # Conclusion: the original paper doesn't provide useful +# # regression tests. +# }) + +test_that("major scales", { + expect_equal(major_scales$`2`, + c(1, 2, 4, 6, 7, 9, 11)) + expect_equal(major_scales$`7`, + c(0, 2, 4, 6, 7, 9, 11)) + expect_equal(minor_scales$`0`, + c(0, 2, 3, 5, 7, 8, 11)) + expect_equal(minor_scales$`9`, + c(0, 2, 4, 5, 8, 9, 11)) +}) + +test_that("rule 1", { + expect_equal(jl_rule_1(c(0, 4, 7)), 1) + expect_equal(jl_rule_1(c(0, 3, 7)), 1) + expect_equal(jl_rule_1(c(3, 7, 10)), 1) + expect_equal(jl_rule_1(c(0, 4, 9)), 1) + expect_equal(jl_rule_1(c(1, 2, 6, 9)), 1) + expect_equal(jl_rule_1(c(0, 2, 5, 9)), 1) + expect_equal(jl_rule_1(c(2, 5, 7, 11)), 1) + expect_equal(jl_rule_1(c(2, 5, 9, 11)), 1) + expect_equal(jl_rule_1(c(0, 4, 8, 9)), 2) + expect_equal(jl_rule_1(c(0, 4, 8, 11)), 2) + expect_equal(jl_rule_1(c(2, 5, 8, 11)), 2) + expect_equal(jl_rule_1(c(3, 5, 9, 11)), 3) + expect_equal(jl_rule_1(c(0, 1, 2, 3, 4)), 3) +}) + +test_that("rule 2", { + expect_false(jl_rule_2(c(2, 6, 9))) + expect_true(jl_rule_2(c(2, 5, 9))) + expect_false(jl_rule_2(c(2, 5, 7, 11))) + expect_true(jl_rule_2(c(2, 3, 7, 11))) +}) + +test_that("rule 3", { + expect_false(jl_rule_3(c(0, 4, 9))) + expect_false(jl_rule_3(c(0, 4, 11))) + expect_true(jl_rule_3(c(0, 2, 7))) +}) diff --git a/tests/testthat/test-par.R b/tests/testthat/test-par.R index 1534721..0d9c924 100644 --- a/tests/testthat/test-par.R +++ b/tests/testthat/test-par.R @@ -1,8 +1,8 @@ context("test-par") test_that("Gill & Purves example", { - a <- bowl18::gill09_harmonicity(c(0, 4, 7)) - b <- bowl18::gill09_harmonicity(c(0, 4, 7), tonic = 3) + a <- gill09_harmonicity(c(0, 4, 7)) + b <- gill09_harmonicity(c(0, 4, 7), tonic = 3) expect_gt(a, b) incon(c(0, 4, 7), model = "gill_09_harmonicity") %>% as.numeric %>% expect_equal(a) incon(c(0, 4, 7), diff --git a/tests/testthat/test-parn88.R b/tests/testthat/test-parn88.R new file mode 100644 index 0000000..ffc7b95 --- /dev/null +++ b/tests/testthat/test-parn88.R @@ -0,0 +1,93 @@ +context("test-encode_pc_set") + +test_that("examples", { + expect_equal(encode_pc_set(c(0, 4, 7)), + as.integer(c(1, 0, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0))) + expect_equal(encode_pc_set(c(0)), + as.integer(c(1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))) + expect_equal(encode_pc_set(integer()), + as.integer(c(0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0))) +}) + +context("test-pc_weight") + +test_that("examples", { + expect_equal( + pc_weight(pc = 0, + pc_set = encode_pc_set(c(0, 4, 7)), + root_support = root_support_weights$v2), + 10 + 3 + 5 + ) + expect_equal( + pc_weight(pc = 1, + pc_set = encode_pc_set(c(0, 4, 7)), + root_support = root_support_weights$v2), + 0 + ) + expect_equal( + pc_weight(pc = 2, + pc_set = encode_pc_set(c(0, 4, 7)), + root_support = root_support_weights$v2), + 2 + 1 + ) + expect_equal( + pc_weight(pc = 4, + pc_set = encode_pc_set(c(0, 4, 7)), + root_support = root_support_weights$v2), + 10 + ) +}) + +context("test-regression") + +library(magrittr) + +test_88 <- function(res, ..., digits = 1) { + expect_equal( + root_ambiguity(c(...), root_support = "v1") %>% round(digits), + res + ) +} + +test_that("Parncutt (1988): Table 4", { + # Dyads + test_88(2.2, 0, 1) + test_88(2.0, 0, 2) + test_88(2.1, 0, 3) + test_88(1.9, 0, 4) + test_88(1.8, 0, 5) + test_88(2.2, 0, 6) + + # Triads + test_88(2.0, 0, 4, 7) + test_88(2.1, 0, 3, 7) + test_88(2.3, 0, 4, 8) + test_88(2.5, 0, 3, 6) + + # Sevenths + test_88(2.1, 0, 4, 7, 10) + test_88(2.3, 0, 3, 7, 10) + test_88(2.3, 0, 4, 7, 11) + test_88(2.4, 0, 3, 6, 10) + test_88(2.9, 0, 3, 6, 9) +}) + +test_that("Sanity checks", { + expect_equal(root(c(0, 4, 7)), 0) + expect_equal(root(c(1, 4, 9)), 9) + expect_gt(root_ambiguity(c(0, 3, 6)), + root_ambiguity(c(0, 4, 7))) +}) + +test_that("root_by_pc_chord", { + chords <- list( + hrep::pc_chord(c(0, 4, 7)), + hrep::pc_chord(c(4, 7, 0)), + hrep::pc_chord(c(7, 2, 5, 11)), + hrep::pc_chord(c(0, 5, 9)) + ) + chord_ids <- purrr::map_int(chords, hrep::encode_pc_chord) + + root_by_pc_chord[chord_ids] %>% + expect_equal(c(0, 0, 7, 5)) +}) diff --git a/tests/testthat/test-parn94.R b/tests/testthat/test-parn94.R new file mode 100644 index 0000000..8706d17 --- /dev/null +++ b/tests/testthat/test-parn94.R @@ -0,0 +1,251 @@ +context("test-cluster_chords") + +library(magrittr) + +# These are cases where there are too many tones masking each other, +# producing an empty complex sonority. + +test_that("examples", { + chord_1 <- c(57, 60, 61, 62, 64, 65) + chord_2 <- c(57, 60, 61, 62, 63, 64, 65) + + complex_sonor(chord_1) %>% expect_equal(0) + pure_sonor(chord_1) %>% expect_equal(0) + multiplicity(chord_1) %>% expect_equal(0) + + expect_equal( + pitch_commonality( + chord_1, chord_2 + ), + as.numeric(NA) + ) + expect_equal( + pitch_distance( + chord_1, chord_2 + ), + 0 + ) +}) +context("test-complex-sonor") + +test_that("testing against legacy code", { + test <- function(x, y, ...) { + expect_equal(complex_sonor(x, ...), y, tolerance = 1e-5) + } + + test(c(60, 64, 67), 0.309965) + test(c(60, 61, 62), 0.0007320671) + test(c(80, 83, 86), 0.181884) + test(c(1, 4, 7), 0) + test(c(40, 54, 67), 0.44699) + + test(c(60, 64, 67), 0.3045464, par = parn94_params(unit_amplitude_in_dB = 50)) + test(c(61, 62, 63), 0.001448646, par = parn94_params(unit_amplitude_in_dB = 70)) + + test(c(60, 64, 67), 0.2271383, par = parn94_params(template_num_harmonics = 5)) + test(c(60, 64, 67), 0.1523711, par = parn94_params(template_roll_off = 2)) + + # HarmonyParncutt::get_parncutt_sonority_analysis( + # c(60, 64, 67), + # parncutt_params = HarmonyParncutt::get_parncutt_params( + # template_roll_off = 2), + # cache = FALSE) +}) +context("test-helpers") + +library(magrittr) + +test_that("get_pure_tone_height matches figures given in Parncutt & Strasburger (1994)", { + expect_equal(round(get_pure_tone_height(0)), 0) + expect_equal(round(get_pure_tone_height(16)), 36) +}) + +test_that("Replicate calculation on pg. 101 of Parncutt & Strasburger (1994)", { + expect_equal(get_partial_masking_level( + masker_auditory_level = 50, + masker_pure_tone_height = get_pure_tone_height(kHz = 0.4), + maskee_auditory_level = 60, + maskee_pure_tone_height = get_pure_tone_height(kHz = 0.5), + k_m = 12 + ) %>% as.numeric %>% round, + 33) + expect_equal(get_partial_masking_level( + maskee_auditory_level = 50, + maskee_pure_tone_height = get_pure_tone_height(kHz = 0.4), + masker_auditory_level = 60, + masker_pure_tone_height = get_pure_tone_height(kHz = 0.5), + k_m = 12 + ) %>% as.numeric %>% round, + 43) +}) + +test_that("Check matrix aspects of partial_masking_level", { + mat <- get_partial_masking_level( + masker_auditory_level = c(50, 60), + masker_pure_tone_height = get_pure_tone_height(c(0.4, 0.5)), + maskee_auditory_level = c(50, 60), + maskee_pure_tone_height = get_pure_tone_height(c(0.4, 0.5)), + k_m = 12 + ) + expect_equal( + mat[1, 2] %>% round, 43 + ) + expect_equal( + mat[2, 1] %>% round, 33 + ) +}) + +test_that("get_overall_masking_level", { + expect_equal(get_overall_masking_level( + auditory_level = 50, + pure_tone_height = get_pure_tone_height(0.5), + k_m = 12 + ), 0) +}) + +test_that("get_pure_tone_audible_level", { + expect_equal( + get_pure_tone_audible_level(auditory_level = 20, overall_masking_level = 10), + 10 + ) + expect_equal( + get_pure_tone_audible_level(auditory_level = 30, overall_masking_level = 50), + 0 + ) +}) + +test_that("get_pure_tone_audibility", { + expect_equal( + get_pure_tone_audibility(pure_tone_audible_level = 0, al_0 = 15), + 0 + ) + expect_gt( + get_pure_tone_audibility(pure_tone_audible_level = 20, al_0 = 15), + 0 + ) + expect_gt( + get_pure_tone_audibility(pure_tone_audible_level = 30, al_0 = 15), + get_pure_tone_audibility(pure_tone_audible_level = 20, al_0 = 15) + ) +}) +context("test-multiplicity") + +test_that("testing against legacy code", { + test <- function(x, y, ...) { + expect_equal(multiplicity(x, ...), y, tolerance = 1e-5) + } + + test(c(60, 64, 67), 2.843946) + test(c(60, 61, 62), 1.293558) + test(c(80, 83, 86), 3.242737) + test(c(40, 54, 67), 2.220517) + + test(c(60, 64, 67), 2.835933, par = parn94_params(unit_amplitude_in_dB = 50)) + test(c(61, 62, 63), 1.293558, par = parn94_params(unit_amplitude_in_dB = 70)) + + test(hrep::sparse_pi_spectrum(c(60, 64, 67), roll_off = 2, digits = 0), + 2.899631) + + # HarmonyParncutt::get_parncutt_sonority_analysis( + # c(60, 64, 67), + # # amplitude = 70, + # # parncutt_params = HarmonyParncutt::get_parncutt_params(), + # midi_params = HarmonyParncutt::get_midi_params(roll_off = 2), + # cache = FALSE)$multiplicity +}) +context("test-pitch_commonality") + +test_that("pitch_commonality", { + expect_equal( + pitch_commonality(c(60, 64, 67), c(60, 64, 67)), + 1 + ) + expect_gt( + # G major should be closer to C major than F# major is to C major + pitch_commonality(c(60, 64, 67), c(59, 62, 67)), + pitch_commonality(c(60, 64, 67), c(61, 66, 68)) + ) + expect_gt( + # G major vs C# major + pitch_commonality(c(60, 64, 67), c(59, 62, 67)), + pitch_commonality(c(60, 64, 67), c(61, 65, 68)) + ) + expect_gt( + # G major vs C transposed + pitch_commonality(c(60, 64, 67), c(48, 76, 79)), + pitch_commonality(c(60, 64, 67), c(59, 62, 67)) + ) + # These numbers are taken from previous versions of this package, + # and have not been compared to other literature/software + expect_equal( + pitch_commonality(c(60, 64, 67), c(48, 76, 79)), + 0.894901857522212, + tolerance = 1e-4 + ) + expect_equal( + pitch_commonality(c(60, 64, 67), c(59, 62, 67)), + 0.349625432417314 + ) +}) +context("test-pitch_distance") + +test_that("pitch_distance", { + expect_equal( + pitch_distance(c(60, 64, 67), c(60, 64, 67)), + 0 + ) + expect_gt( + # Presumably C# major should be closer in pitch to + # C major than e.g. F major + pitch_distance(c(60, 64, 67), c(65, 69, 72)), + pitch_distance(c(60, 64, 67), c(61, 65, 68)) + ) + # These numbers are taken from previous versions of this package, + # and have not been compared to other literature/software + expect_equal( + pitch_distance(c(60, 64, 67), c(65, 69, 72)), + 3.86723877405512 + ) + expect_equal( + pitch_distance(c(60, 64, 67), c(65, 63, 83)), + 37.8133050960468 + ) +}) +context("test-pitch_salience") + +library(magrittr) + +test_that("pitch_salience", { + x <- .parn94() + x$combined_spectrum <- data.frame(pitch = 1:5, salience = 1) + x$par <- list(min_midi = 0, max_midi = 10) + expect_equal( + pitch_salience(x) %>% as.numeric, + c(0, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0) + ) +}) +context("test-pure-sonor") + +test_that("testing against legacy code", { + test <- function(x, y, ...) { + expect_equal(pure_sonor(x, ...), y, tolerance = 1e-5) + } + + test(c(60, 64, 67), 0.6157366) + test(c(60, 61, 62), 0.005490503) + test(c(80, 83, 86), 0.6535714) + test(c(1, 4, 7), 0) + test(c(40, 54, 67), 0.5373542) + + test(c(60, 64, 67), 0.6061362, par = parn94_params(unit_amplitude_in_dB = 50)) + test(c(61, 62, 63), 0.01086485, par = parn94_params(unit_amplitude_in_dB = 70)) + + test(hrep::sparse_pi_spectrum(c(60, 64, 67), roll_off = 2, digits = 0), + 0.6136948) + + # HarmonyParncutt::get_parncutt_sonority_analysis( + # c(60, 64, 67), + # parncutt_params = HarmonyParncutt::get_parncutt_params(), + # midi_params = HarmonyParncutt::get_midi_params(roll_off = 2), + # cache = FALSE)$pure_sonorousness +}) diff --git a/tests/testthat/test-stolz15.R b/tests/testthat/test-stolz15.R new file mode 100644 index 0000000..7aa9918 --- /dev/null +++ b/tests/testthat/test-stolz15.R @@ -0,0 +1,69 @@ +context("test-misc") + +context("stolzenburg") + +library(magrittr) + +test_that("approximating fractions", { + # These examples are taken from Table 1 of + # 10.1080/17459737.2015.1033024 + expect_equal(stolz15_fraction(1.059, 0.01), c(16, 15)) + expect_equal(stolz15_fraction(1.122, 0.01), c(9, 8)) + expect_equal(stolz15_fraction(1.189, 0.01), c(6, 5)) + expect_equal(stolz15_fraction(1.260, 0.01), c(5, 4)) + expect_equal(stolz15_fraction(1.335, 0.011), c(4, 3)) + expect_equal(stolz15_fraction(1.414, 0.011), c(7, 5)) + expect_equal(stolz15_fraction(1.498, 0.011), c(3, 2)) + expect_equal(stolz15_fraction(1.587, 0.011), c(8, 5)) +}) + +test_that("double fraction", { + expect_equal(double_fraction(c(3, 4)), c(3, 2)) + expect_equal(double_fraction(c(3, 7)), c(6, 7)) +}) + +test_that("double fraction", { + expect_equal(half_fraction(c(6, 5)), c(3, 5)) + expect_equal(half_fraction(c(3, 7)), c(3, 14)) +}) + +test_that("get_rational_interval_2", { + expect_equal(get_rational_interval(0), c(1, 1)) + expect_equal(get_rational_interval(12), c(2, 1)) + expect_equal(get_rational_interval(-3), c(5, 6)) + expect_equal(get_rational_interval(-9), c(3, 5)) + expect_equal(get_rational_interval(-6), c(7, 10)) +}) + +test_that("least common multiple", { + expect_equal(stolz15_lcm(c(4, 6)), 12) + expect_equal(stolz15_lcm(c(21, 6)), 42) + expect_equal(stolz15_lcm(c(8, 9, 21)), 504) +}) + +test_that("smooth_log_periodicity", { + expect_equal(smooth_log_periodicity(c(0, 3, 9)) %>% round(digits = 1), + 3.7) + expect_equal(smooth_log_periodicity(c(48, 64, 67)), 1) + expect_equal(smooth_log_periodicity(c(0, 3, 7)), log2(10)) + expect_equal(smooth_log_periodicity(c(0:11)) %>% round(digits = 1), 7.4) +}) + +context("test-regression") + +test_that("regression tests", { + df <- read.csv(system.file("stolz15/data-formatted.csv", + package = "incon"), + stringsAsFactors = FALSE) + chords <- lapply(strsplit(gsub("\\{|\\}", "", df$chord), + split = ","), + as.integer) + + for (i in seq_along(chords)) { + expect_equal( + smooth_log_periodicity(chords[[i]]), + df$periodicity.stolz_smooth_t2_log[[i]], + tolerance = 1e-3 + ) + } +}) diff --git a/tests/testthat/test-wang13.R b/tests/testthat/test-wang13.R new file mode 100644 index 0000000..6b32d27 --- /dev/null +++ b/tests/testthat/test-wang13.R @@ -0,0 +1,27 @@ +context("test-wang13") + +test_that("from legacy implementation", { + # C major + expect_equal( + roughness_wang(c(48, 64, 67)), + 0.67, + tolerance = 1e-2 + ) + expect_equal( + roughness_wang(c(48, 64, 67), include_phase_impact_factors = TRUE), + 0.11, + tolerance = 1e-2 + ) + + # C dim + expect_equal( + roughness_wang(c(48, 63, 66)), + 0.82, + tolerance = 1e-2 + ) + expect_equal( + roughness_wang(c(48, 63, 66), include_phase_impact_factors = TRUE), + 0.10, + tolerance = 1e-2 + ) +}) diff --git a/tests/testthat/testthat-problems.rds b/tests/testthat/testthat-problems.rds new file mode 100644 index 0000000..6adadd8 Binary files /dev/null and b/tests/testthat/testthat-problems.rds differ