R/visvow.R

Defines functions visvow vowelNormD optionsNormal optionsScale vowelNormF vowelLongD vowelLong4 vowelLong3 vowelLong2 vowelLong1 vowelScale Scale

Documented in visvow

#' @name
#' visvow
#'
#' @title
#' Visible Vowels
#'
#' @aliases
#' visvow
#'
#' @description
#' Visible Vowels is an app that visualizes vowel variation in f0, F1, F2, F3 and duration.
#'
#' A vowel is a speech sound produced without audible impediment to the airflow in the mouth and/or throat.
#' Each vowel has a particular pitch (except when whispered), quality (timbre) and duration.
#' f0 is the fundamental frequency of the periodic waveform and determines the perceived pitch.
#' The quality is determined by the formants.
#' Formants are resonance frequencies that define the spectral shape of vowels (and vowel-like sounds).
#' The formant with the lowest frequency is called F1, the second-lowest F2, and the third F3.
#' F1 is correlated with tongue height.
#' The closer the tongue approaches the palate, the lower F1.
#' F2 correlates with tongue retraction and lip protrusion.
#' The more the tongue is positioned towards the front of the mouth, and the wider the lips are spread, the higher F2.
#' F3 correlates with the tongue-blade position.
#' The closer the blade is to the lips, the higher is F3.
#' The acoustic vowel duration primarily corresponds with the perceived duration of a vowel sound.
#' See Johnson (2012).
#'
#' @usage
#' visvow()
#'
#' @format
#' NULL
#'
#' @details
#' \code{visvow()} opens Visible Vowels in your default web browser.
#'
#' @seealso
#' The Help tab in the app provides more information about the format of the input file.
#' Details about scale conversion and speaker normalization procedures and some specific metrics are found in the vignette.
#'
#' @references
#' \insertRef{johnson:2012}{visvow}
#'
#' @import
#' shiny shinyBS tidyr PBSmapping ggplot2 plot3D MASS ggdendro ggrepel readxl WriteXLS pracma Rtsne grid svglite Cairo tikzDevice shinybusy
#' 
#' @importFrom 
#' stats sd predict aov manova
#'
#' @importFrom
#' formattable renderFormattable formattable formatter style color_tile formattableOutput
#' 
#' @importFrom
#' splitstackshape expandRows
#' 
#' @importFrom 
#' plyr .
#'
#' @importFrom
#' effectsize eta_squared
#'
#' @importFrom
#' vegan adonis2
#' 
#' @importFrom
#' Rdpack reprompt
#'
#' @export
#' visvow
NULL

################################################################################

Scale <- function(h,replyScale,Ref)
{
  if (replyScale==" Hz")
  {
    s <- h
  }

  if (replyScale==" bark I")
  {
    s = 7*log(h/650+sqrt(1+(h/650)^2))
  }

  if (replyScale==" bark II")
  {
    s = 13 * atan(0.00076*h) + 3.5 * atan((h/7500)^2)
  }

  if (replyScale==" bark III")
  {
    s <- (26.81 * (h/(1960+h))) - 0.53

    s[which(s< 2  )] <- s[which(s< 2  )] + (0.15 * (2-s[which(s< 2  )]     ))
    s[which(s>20.1)] <- s[which(s>20.1)] + (0.22 * (  s[which(s>20.1)]-20.1))
  }

  if (replyScale==" ERB I")
  {
    s <- 16.7 * log10(1 + (h/165.4))
  }

  if (replyScale==" ERB II")
  {
    s <- 11.17 * log((h+312) / (h+14675)) + 43
  }

  if (replyScale==" ERB III")
  {
    s <- 21.4 * log10((0.00437*h)+1)
  }

  if (replyScale==" ln")
  {
    s <- log(h)
  }

  if (replyScale==" mel I")
  {
    s <- (1000/log10(2)) * log10((h/1000) + 1)
  }

  if (replyScale==" mel II")
  {
    s <- 1127 * log(1 + (h/700))
  }

  if (replyScale==" ST")
  {
    s <- 12 * log2(h/Ref)
  }

  return(s)
}

vowelScale <- function(vowelTab, replyScale, Ref)
{
  if (is.null(vowelTab))
    return(NULL)

  indexVowel <- grep("^vowel$", colnames(vowelTab))
  nColumns   <- ncol(vowelTab)
  nPoints    <- (nColumns - (indexVowel + 1))/5

  vT <- vowelTab

  for (i in (1:nPoints))
  {
    indexTime <- indexVowel + 2 + ((i-1)*5)

    for (j in ((indexTime+1):(indexTime+4)))
    {
      vT[,j] <- Scale(vT[,j],replyScale, Ref)
    }
  }

  return(vT)
}

################################################################################

vowelLong1 <- function(vowelScale,replyTimesN)
{
  indexVowel <- grep("^vowel$", colnames(vowelScale))
  nColumns   <- ncol(vowelScale)
  nPoints    <- (nColumns - (indexVowel + 1))/5

  vT <- data.frame()

  for (i in (1:nPoints))
  {
    if (is.element(as.character(i),replyTimesN))
    {
      indexTime <- indexVowel + 2 + ((i-1)*5)

      vTsub <- data.frame(speaker = vowelScale[,1],
                          vowel   = vowelScale[,indexVowel],
                          point   = i,
                          f0      = vowelScale[,indexTime+1],
                          f1      = vowelScale[,indexTime+2],
                          f2      = vowelScale[,indexTime+3],
                          f3      = vowelScale[,indexTime+4])

      vT <- rbind(vT,vTsub)
    }
  }

  return(vT)
}

vowelLong2 <- function(vowelLong1)
{
  vT <- data.frame()

  for (j in (1:2))
  {
    vTsub <- data.frame(speaker = vowelLong1$speaker,
                        vowel   = vowelLong1$vowel,
                        point   = vowelLong1$point,
                        formant = j,
                        f       = log(vowelLong1[,j+4]))
    
    if (all(is.infinite(vTsub$f)))
      vTsub$f <- 0
    
    vT <- rbind(vT,vTsub)
  }

  return(vT)
}

vowelLong3 <- function(vowelLong1)
{
  vT <- data.frame()
  
  for (j in (1:3))
  {
    vTsub <- data.frame(speaker = as.factor(vowelLong1$speaker),
                        vowel   = as.factor(vowelLong1$vowel),
                        point   = as.factor(vowelLong1$point),
                        formant = as.factor(j),
                        f       = log(vowelLong1[,j+4]))
    
    if (all(is.infinite(vTsub$f)))
      vTsub$f <- 0
    
    vT <- rbind(vT,vTsub)
  }
  
  return(vT)
}

vowelLong4 <- function(vowelLong1)
{
  vT <- data.frame()
  
  for (j in (0:3))
  {
    vTsub <- data.frame(speaker = as.factor(vowelLong1$speaker),
                        vowel   = as.factor(vowelLong1$vowel),
                        point   = as.factor(vowelLong1$point),
                        formant = as.factor(j),
                        f       = log(vowelLong1[,j+4]))
    
    if (all(is.infinite(vTsub$f)))
      vTsub$f <- 0
    
    vT <- rbind(vT,vTsub)
  }
  
  return(vT)
}

vowelLongD <- function(vowelLong1)
{
  vT <- data.frame()
  
  for (j in (1:3))
  {
    vTsub <- data.frame(speaker = vowelLong1$speaker,
                        vowel   = vowelLong1$vowel,
                        point   = vowelLong1$point,
                        formant = j,
                        f       = vowelLong1[,j+4])
    
    vT <- rbind(vT,vTsub)
  }
  
  return(vT)
}

vowelNormF <- function(vowelScale,vowelLong1,vowelLong2,vowelLong3,vowelLong4,vowelLongD,replyNormal)
{
  if (is.null(vowelScale))
    return(NULL)

  indexVowel <- grep("^vowel$", colnames(vowelScale))
  nColumns   <- ncol(vowelScale)
  nPoints    <- (nColumns - (indexVowel + 1))/5

   SpeaKer   <- unique(vowelScale[,1])
  nSpeaKer   <- length(SpeaKer)

   VoWel     <- unique(vowelScale[,indexVowel])
  nVoWel     <- length(VoWel)

  if (replyNormal=="")
  {
    vT <- vowelScale
  }

  if (replyNormal==" Peterson")
  {
    vT <- vowelScale
    
    for (i in (1:nPoints))
    {
      indexTime <- indexVowel + 2 + ((i-1)*5)
      
      vT[,indexTime+1] <- vT[,indexTime+1]/vT[,indexTime+4]
      vT[,indexTime+2] <- vT[,indexTime+2]/vT[,indexTime+4]
      vT[,indexTime+3] <- vT[,indexTime+3]/vT[,indexTime+4]
    }
  }
  
  if (replyNormal==" Sussman")
  {
    vT <- vowelScale

    for (i in (1:nPoints))
    {
      indexTime <- indexVowel + 2 + ((i-1)*5)

      F123 <- apply(vT[, indexTime+2:4], 1, psych::geometric.mean)

      vT[,indexTime+2] <- log(vT[,indexTime+2]/F123)
      vT[,indexTime+3] <- log(vT[,indexTime+3]/F123)
      vT[,indexTime+4] <- log(vT[,indexTime+4]/F123)
    }
  }

  if (replyNormal==" Syrdal & Gopal")
  {
    vT <- vowelScale

    for (i in (1:nPoints))
    {
      indexTime <- indexVowel + 2 + ((i-1)*5)

      vT[,indexTime+2] <-  1 * (vT[,indexTime+2] - vT[,indexTime+1])
      vT[,indexTime+3] <- -1 * (vT[,indexTime+4] - vT[,indexTime+3])
    }
  }

  if (replyNormal==" Miller")
  {
    vT <- data.frame()
    vTLong1Ag <- stats::aggregate(f0~speaker+vowel+point, data=vowelLong1, FUN=psych::geometric.mean)

    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])

      GMf0 <- psych::geometric.mean(vTLong1AgSub$f0)
      SR   <- 168*((GMf0/168)^(1/3))

      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])

      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)

        vTsub[,indexTime+4] <- log(vTsub[,indexTime+4] / vTsub[,indexTime+3])
        vTsub[,indexTime+3] <- log(vTsub[,indexTime+3] / vTsub[,indexTime+2])
        vTsub[,indexTime+2] <- log(vTsub[,indexTime+2] / SR)
      }

      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" Thomas & Kendall")
  {
    vT <- vowelScale
    
    for (i in (1:nPoints))
    {
      indexTime <- indexVowel + 2 + ((i-1)*5)
      
      vT[,indexTime+1] <- -1 * (vT[,indexTime+4] - vT[,indexTime+1])
      vT[,indexTime+2] <- -1 * (vT[,indexTime+4] - vT[,indexTime+2])
      vT[,indexTime+3] <- -1 * (vT[,indexTime+4] - vT[,indexTime+3])
    }
  }

  if (replyNormal==" Gerstman")
  {
    vT <- data.frame()
    
    vTLong1Ag <- stats::aggregate(cbind(f0,f1,f2,f3)~speaker+vowel+point, data=vowelLong1, FUN=mean)
    vTLong1Ag <- stats::aggregate(cbind(f0,f1,f2,f3)~speaker+vowel      , data=vTLong1Ag , FUN=mean)

    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])

      minF <- rep(0,4)
      maxF <- rep(0,4)
      
      for (j in 1:4)
      {
        minF[j] <- min(vTLong1AgSub[,j+2])
        maxF[j] <- max(vTLong1AgSub[,j+2])
      }
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (1:4))
        {
          vTsub[,j+indexTime] <- 999 * ((vTsub[,j+indexTime]-minF[j]) / (maxF[j]-minF[j]))
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }

  if ((replyNormal==" Lobanov") & (nVoWel==1))
  {
    vT <- data.frame()
    vTLong1Ag <- vowelLong1
    
    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])
      
      meanF <- rep(0,4)
        sdF <- rep(0,4)
      
      for (j in 1:4)
      {
        meanF[j] <-      mean(vTLong1AgSub[,j+3])
          sdF[j] <- stats::sd(vTLong1AgSub[,j+3])
      }
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (1:4))
        {
          vTsub[,j+indexTime] <- (vTsub[,j+indexTime]-meanF[j])/sdF[j]
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }
  
  if ((replyNormal==" Lobanov") & (nVoWel> 1))
  {
    vT <- data.frame()
    vTLong1Ag <- stats::aggregate(cbind(f0,f1,f2,f3)~speaker+vowel+point, data=vowelLong1, FUN=mean)
    
    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])
      
      meanF <- rep(0,4)
        sdF <- rep(0,4)
      
      for (j in 1:4)
      {
        meanF[j] <-      mean(vTLong1AgSub[,j+3])
          sdF[j] <- stats::sd(vTLong1AgSub[,j+3])
      }
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (1:4))
        {
          vTsub[,j+indexTime] <- (vTsub[,j+indexTime]-meanF[j])/sdF[j]
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" Watt & Fabricius")
  {
    vT <- data.frame()
    vTLong1Ag <- stats::aggregate(cbind(f0,f1,f2,f3)~speaker+vowel+point, data=vowelLong1, FUN=mean)

    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])

      vowelF1 <- stats::aggregate(f1~vowel, data=vTLong1AgSub, FUN=mean)
      vowelF2 <- stats::aggregate(f2~vowel, data=vTLong1AgSub, FUN=mean)

      iF1 <- min(vowelF1$f1)
      iF2 <- max(vowelF2$f2)

      uF1 <- min(vowelF1$f1)
      uF2 <- min(vowelF1$f1)

      aF1 <- max(vowelF1$f1)
      aF2 <- vowelF2$f2[which(vowelF1$f1 == aF1)]

      centroidF1 <- (iF1 + uF1 + aF1)/3
      centroidF2 <- (iF2 + uF2 + aF2)/3

      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])

      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)

        vTsub[,indexTime+2] <- vTsub[,indexTime+2] / centroidF1
        vTsub[,indexTime+3] <- vTsub[,indexTime+3] / centroidF2
      }

      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" Fabricius et al.")
  {
    vT <- data.frame()
    vTLong1Ag <- stats::aggregate(cbind(f0,f1,f2,f3)~speaker+vowel+point, data=vowelLong1, FUN=mean)

    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])

      vowelF1 <- stats::aggregate(f1~vowel, data=vTLong1AgSub, FUN=mean)
      vowelF2 <- stats::aggregate(f2~vowel, data=vTLong1AgSub, FUN=mean)

      iF1 <- min(vowelF1$f1)
      iF2 <- max(vowelF2$f2)

      uF1 <- min(vowelF1$f1)
      uF2 <- min(vowelF1$f1)

      aF1 <- max(vowelF1$f1)

      centroidF1 <- (iF1 + uF1 + aF1)/3
      centroidF2 <- (iF2 + uF2      )/2

      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])

      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)

        vTsub[,indexTime+2] <- vTsub[,indexTime+2] / centroidF1
        vTsub[,indexTime+3] <- vTsub[,indexTime+3] / centroidF2
      }

      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" Bigham")
  {
    vT <- data.frame()
    vTLong1Ag <- stats::aggregate(cbind(f0,f1,f2,f3)~speaker+vowel+point, data=vowelLong1, FUN=mean)
    
    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])
      
      vowelF1 <- stats::aggregate(f1~vowel, data=vTLong1AgSub, FUN=mean)
      vowelF2 <- stats::aggregate(f2~vowel, data=vTLong1AgSub, FUN=mean)
      
      iF1 <- min(vowelF1$f1)
      iF2 <- max(vowelF2$f2)
      
      uF1 <- min(vowelF1$f1)
      uF2 <- min(vowelF2$f2)
      
      oF1 <- max(vowelF1$f1)
      oF2 <- min(vowelF2$f2)
      
      aF1 <- max(vowelF1$f1)

                          index <- which(vowelF2$vowel ==        intToUtf8("0x00E6"))
      if (!length(index)) index <- which(vowelF2$vowel == paste0(intToUtf8("0x00E6"), intToUtf8("0x02D1")))
      if (!length(index)) index <- which(vowelF2$vowel == paste0(intToUtf8("0x00E6"), intToUtf8("0x02D0")))
      if (!length(index)) index <- which(vowelF2$vowel ==        intToUtf8("0x0061"))
      if (!length(index)) index <- which(vowelF2$vowel == paste0(intToUtf8("0x0061"), intToUtf8("0x02D1")))
      if (!length(index)) index <- which(vowelF2$vowel == paste0(intToUtf8("0x0061"), intToUtf8("0x02D0")))
      if (!length(index)) index <- which(vowelF2$vowel ==        intToUtf8("0x025B"))
      if (!length(index)) index <- which(vowelF2$vowel == paste0(intToUtf8("0x025B"), intToUtf8("0x02D1")))
      if (!length(index)) index <- which(vowelF2$vowel == paste0(intToUtf8("0x025B"), intToUtf8("0x02D0")))
                          
      aF2 <- vowelF2$f2[index]

      centroidF1 <- (iF1 + uF1 + oF1 + aF1)/4
      centroidF2 <- (iF2 + uF2 + oF2 + aF2)/4
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        vTsub[,indexTime+2] <- vTsub[,indexTime+2] / centroidF1
        vTsub[,indexTime+3] <- vTsub[,indexTime+3] / centroidF2
      }
      
      vT <- rbind(vT,vTsub)
    }
  }
  
  if (replyNormal==" Heeringa & Van de Velde I")
  {
    vT <- data.frame()
    vTLong1Ag <- stats::aggregate(cbind(f0,f1,f2,f3)~speaker+vowel+point, data=vowelLong1, FUN=mean)
    
    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])
      
      vowelF1 <- stats::aggregate(f1~vowel, data=vTLong1AgSub, FUN=mean)
      vowelF2 <- stats::aggregate(f2~vowel, data=vTLong1AgSub, FUN=mean)
      
      k <- grDevices::chull(vowelF1$f1,vowelF2$f2)
      
      xx <- vowelF1$f1[k]
      xx[length(xx)+1] <- xx[1]
      yy <- vowelF2$f2[k]
      yy[length(yy)+1] <- yy[1]
      
      if ((length(xx)>=3) & (length(yy)>=3))
        pc <- pracma::poly_center(xx,yy)
      else
        pc <- c(mean(xx),mean(yy))
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        vTsub[,indexTime+2] <- vTsub[,indexTime+2]/pc[1]
        vTsub[,indexTime+3] <- vTsub[,indexTime+3]/pc[2]
      }
      
      vT <- rbind(vT,vTsub)
    }
  }
  
  if (replyNormal==" Heeringa & Van de Velde II")
  {
    vT <- data.frame()
    vTLong1Ag <- stats::aggregate(cbind(f0,f1,f2,f3)~speaker+vowel+point, data=vowelLong1, FUN=mean)

    for (q in (1:nSpeaKer))
    {
      vTLong1AgSub <- subset(vTLong1Ag, speaker==SpeaKer[q])

      vowelF1 <- stats::aggregate(f1~vowel, data=vTLong1AgSub, FUN=mean)
      vowelF2 <- stats::aggregate(f2~vowel, data=vTLong1AgSub, FUN=mean)

      k <- grDevices::chull(vowelF1$f1,vowelF2$f2)

      xx <- vowelF1$f1[k]
      xx[length(xx)+1] <- xx[1]
      yy <- vowelF2$f2[k]
      yy[length(yy)+1] <- yy[1]

      if ((length(xx)>=3) & (length(yy)>=3))
        pc <- pracma::poly_center(xx,yy)
      else
        pc <- c(mean(vowelF1$f1[k]),mean(vowelF2$f2[k]))
      
      xxi <- stats::approx(1:length(xx), xx, n = 1000)$y
      yyi <- stats::approx(1:length(yy), yy, n = 1000)$y
      
      xxi <- xxi[1:(length(xxi)-1)]
      yyi <- yyi[1:(length(yyi)-1)]

      xxg <- cut(xxi, breaks=10)
      yyg <- cut(yyi, breaks=10)

      ag <- stats::aggregate(cbind(xxi,yyi)~xxg+yyg, FUN=mean)

      mean1 <- mean(ag$xxi)
      mean2 <- mean(ag$yyi)
      
      sd1 <- stats::sd(ag$xxi)
      sd2 <- stats::sd(ag$yyi)

      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])

      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)

        vTsub[,indexTime+2] <- (vTsub[,indexTime+2]-pc[1])/sd1
        vTsub[,indexTime+3] <- (vTsub[,indexTime+3]-pc[2])/sd2
      }

      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" Nearey I")
  {
    vT <- data.frame()
    vTLong3Ag <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLong4, FUN=mean)
    
    for (q in (1:nSpeaKer))
    {
      vTLong3AgSub  <- subset(vTLong3Ag, speaker==SpeaKer[q])
      speakerMean   <- stats::aggregate(f~formant, data=vTLong3AgSub, FUN=mean)
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (1:4))
        {
          vTsub[,j+indexTime] <- log(vTsub[,j+indexTime]) - speakerMean$f[j]
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }
  
  if (replyNormal==" Nearey II")
  {
    vT <- data.frame()
    vTLong3Ag <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLong3, FUN=mean)
    
    for (q in (1:nSpeaKer))
    {
      vTLong3AgSub  <- subset(vTLong3Ag, speaker==SpeaKer[q])
      speakerMean   <- mean(vTLong3AgSub$f)
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (2:4))
        {
          vTsub[,j+indexTime] <- log(vTsub[,j+indexTime]) - speakerMean
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" Barreda & Nearey I")
  {
    vTLong3Ag <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLong4, FUN=mean)
    
    S <- list()
    
    for (i in 0:3)
    {
      vTLong3AgSub <- subset(vTLong3Ag, vTLong3Ag$formant==i)
      
      N <- factor(interaction(vTLong3AgSub$vowel,vTLong3AgSub$point))
      M <- stats::lm(f~0+speaker+N, contrasts = list(N=stats::contr.sum), data = vTLong3AgSub)
      S[[i+1]] <- as.data.frame(stats::dummy.coef(M)$speaker)
    }
    
    vT <- data.frame()
    
    for (q in (1:nSpeaKer))
    {
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (1:4))
        {
          speakerMeanR <- S[[j]][rownames(S[[j]])==SpeaKer[q],1]
          vTsub[,j+indexTime] <- log(vTsub[,j+indexTime]) - speakerMeanR
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }
  
  if (replyNormal==" Barreda & Nearey II")
  {
    vTLong3Ag <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLong3, FUN=mean)
    
    N <- factor(interaction(vTLong3Ag$vowel,vTLong3Ag$point,vTLong3Ag$formant))
    M <- stats::lm(f~0+speaker+N, contrasts = list(N=stats::contr.sum), data = vTLong3Ag)
    S <- as.data.frame(stats::dummy.coef(M)$speaker)
    
    vT <- data.frame()
    
    for (q in (1:nSpeaKer))
    {
      speakerMeanR <- S[rownames(S)==SpeaKer[q],1]
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (2:4))
        {
          vTsub[,j+indexTime] <- log(vTsub[,j+indexTime]) - speakerMeanR
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" Labov I")
  {
    vT <- data.frame()

    vTLong2Ag <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLong2, FUN=mean)
    grandMean <- mean(vTLong2Ag$f)

    for (q in (1:nSpeaKer))
    {
      vTLong2AgSub  <- subset(vTLong2Ag, speaker==SpeaKer[q])
      speakerMean   <- psych::geometric.mean(vTLong2AgSub$f)
      speakerFactor <- exp(grandMean - speakerMean)

      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])

      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)

        for (j in (2:3))
        {
          vTsub[,j+indexTime] <- speakerFactor * vTsub[,j+indexTime]
        }
      }

      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" LABOV I")
  {
    vT <- data.frame()
    
    vTLong2Ag <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLong2, FUN=psych::geometric.mean)
    grandMean <- psych::geometric.mean(vTLong2Ag$f)
    
    for (q in (1:nSpeaKer))
    {
      vTLong2AgSub  <- subset(vTLong2Ag, speaker==SpeaKer[q])
      speakerMean   <- psych::geometric.mean(vTLong2AgSub$f)
      speakerFactor <- exp(grandMean - speakerMean)
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (2:3))
        {
          vTsub[,j+indexTime] <- speakerFactor * vTsub[,j+indexTime]
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }
  
  if (replyNormal==" Labov II")
  {
    vT <- data.frame()

    vTLong3Ag <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLong3, FUN=mean)
    grandMean <- mean(vTLong3Ag$f)

    for (q in (1:nSpeaKer))
    {
      vTLong3AgSub  <- subset(vTLong3Ag, speaker==SpeaKer[q])
      speakerMean   <- psych::geometric.mean(vTLong3AgSub$f)
      speakerFactor <- exp(grandMean - speakerMean)

      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])

      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)

        for (j in (2:4))
        {
          vTsub[,j+indexTime] <- speakerFactor * vTsub[,j+indexTime]
        }
      }

      vT <- rbind(vT,vTsub)
    }
  }

  if (replyNormal==" LABOV II")
  {
    vT <- data.frame()
    
    vTLong3Ag <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLong3, FUN=psych::geometric.mean)
    grandMean <- psych::geometric.mean(vTLong3Ag$f)
    
    for (q in (1:nSpeaKer))
    {
      vTLong3AgSub  <- subset(vTLong3Ag, speaker==SpeaKer[q])
      speakerMean   <- psych::geometric.mean(vTLong3AgSub$f)
      speakerFactor <- exp(grandMean - speakerMean)
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (2:4))
        {
          vTsub[,j+indexTime] <- speakerFactor * vTsub[,j+indexTime]
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }
  
  if (replyNormal==" Johnson")
  {
    vT <- data.frame()
    vTLongDAg <- stats::aggregate(f~speaker+vowel+point+formant, data=vowelLongD, FUN=mean)
    
    for (q in (1:nSpeaKer))
    {
      vTLongDAgSub  <- subset(vTLongDAg, speaker==SpeaKer[q])
      deltaF <- mean(vTLongDAgSub$f/(vTLongDAgSub$formant - 0.5))
      
      vTsub <- subset(vowelScale, vowelScale[,1]==SpeaKer[q])
      
      for (i in (1:nPoints))
      {
        indexTime <- indexVowel + 2 + ((i-1)*5)
        
        for (j in (2:4))
        {
          vTsub[,j+indexTime] <- vTsub[,j+indexTime]/deltaF
        }
      }
      
      vT <- rbind(vT,vTsub)
    }
  }

  return(vT)
}

################################################################################

optionsScale <- function()
{
  options <- c("Hz" = " Hz",
               "bark: Schroeder et al. (1979)" = " bark I",
               "bark: Zwicker & Terhardt (1980)" = " bark II",
               "bark: Traunmueller (1990)" = " bark III",
               "ERB: Greenwood (1961)" = " ERB I",
               "ERB: Moore & Glasberg (1983)" = " ERB II",
               "ERB: Glasberg & Moore (1990)" = " ERB III",
               "ln" = " ln",
               "mel: Fant (1968)" = " mel I",
               "mel: O'Shaughnessy (1987)" = " mel II",
               "ST" = " ST")

  return(options)
}

optionsNormal <- function(vowelTab, replyScale, noSelF0, noSelF3)
{
   SpeaKer <- unique(vowelTab[,1])
  nSpeaKer <- length(SpeaKer)
  
  if (nSpeaKer==1)
    return(c("None" = ""))

  indexVowel <- grep("^vowel$", colnames(vowelTab))

  nonEmptyF0 <- (sum(vowelTab[,indexVowel+3]==0)!=nrow(vowelTab))
  nonEmptyF3 <- (sum(vowelTab[,indexVowel+6]==0)!=nrow(vowelTab))
  
  ###
  
  options1 <- c()
  
  if (nonEmptyF3 & noSelF3)
    options1 <- c(options1, "Peterson (1951)" = " Peterson")
  
  if (nonEmptyF3 & noSelF0 & (replyScale==" Hz"))
    options1 <- c(options1, "Sussman (1986)" = " Sussman")
  
  if (nonEmptyF0 & nonEmptyF3 & noSelF0 & noSelF3)
    options1 <- c(options1, "Syrdal & Gopal (1986)" = " Syrdal & Gopal")
  
  if (nonEmptyF0 & noSelF0 & (replyScale==" Hz"))
    options1 <- c(options1, "Miller (1989)" = " Miller")
  
  if (nonEmptyF3 & noSelF3)
    options1 <- c(options1, "Thomas & Kendall (2007)" = " Thomas & Kendall")
  
  ###
  
  options2 <- c()
  
  options2 <- c(options2, "Gerstman (1968)" = " Gerstman")
  
  ###
  
  options3 <- c()
  
  options3 <- c(options3, "Lobanov (1971)" = " Lobanov")
  
  if (noSelF0 & noSelF3)
    options3 <- c(options3, "Watt & Fabricius (2002)" = " Watt & Fabricius")
  
  if (noSelF0 & noSelF3)
    options3 <- c(options3, "Fabricius et al. (2009)" = " Fabricius et al.")
  
  if (noSelF0 & noSelF3)
    options3 <- c(options3, "Bigham (2008)" = " Bigham")
  
  if (noSelF0 & noSelF3)
    options3 <- c(options3, "Heeringa & Van de Velde (2021) I"  = " Heeringa & Van de Velde I" )
  
  if (noSelF0 & noSelF3)
    options3 <- c(options3, "Heeringa & Van de Velde (2021) II" = " Heeringa & Van de Velde II")
  
  ###
  
  options4 <- c()
  
  if (replyScale==" Hz")
    options4 <- c(options4, "Nearey (1978) I"  = " Nearey I" )
  
  if (noSelF0 & nonEmptyF3 & (replyScale==" Hz"))
    options4 <- c(options4, "Nearey (1978) II" = " Nearey II")
  
  if (replyScale==" Hz")
    options4 <- c(options4, "Barreda & Nearey (2018) I"  = " Barreda & Nearey I" )
  
  if (noSelF0 & nonEmptyF3 & (replyScale==" Hz"))
    options4 <- c(options4, "Barreda & Nearey (2018) II" = " Barreda & Nearey II")
  
  if (noSelF0 & noSelF3 & (replyScale==" Hz"))
    options4 <- c(options4, "Labov (2006) log-mean I"  = " Labov I" )
  
  if (noSelF0 & noSelF3 & (replyScale==" Hz"))
    options4 <- c(options4, "Labov (2006) log-geomean I"  = " LABOV I" )
  
  if (nonEmptyF3 & noSelF0 & (replyScale==" Hz"))
    options4 <- c(options4, "Labov (2006) log-mean II" = " Labov II")
  
  if (nonEmptyF3 & noSelF0 & (replyScale==" Hz"))
    options4 <- c(options4, "Labov (2006) log-geomean II" = " LABOV II")
  
  if (nonEmptyF3 & noSelF0)
    options4 <- c(options4, "Johnson (2018)" = " Johnson")
  
  ###

  return(c("None" = "", list(" Formant-ratio normalization"=options1,
                             " Range normalization"        =options2,
                             " Centroid normalization"     =options3,
                             " Log-mean normalization"     =options4)))
}

################################################################################

vowelNormD <- function(vowelTab,replyNormal)
{
  if ((is.null(vowelTab)) || (length(replyNormal)==0))
    return(NULL)

  indexVowel <- grep("^vowel$", colnames(vowelTab))

   SpeaKer <- unique(vowelTab[,1])
  nSpeaKer <- length(SpeaKer)

   VoWel   <- unique(vowelTab[,indexVowel])
  nVoWel   <- length(VoWel)

  if (replyNormal=="")
  {
    vT <- vowelTab
  }

  if ((replyNormal==" Lobanov") & (nVoWel==1))
  {
    vT <- data.frame()
    vTAg <- data.frame(vowelTab[,1],vowelTab[,indexVowel+1])

    for (q in (1:nSpeaKer))
    {
      vTAgSub <- subset(vTAg, vTAg[,1]==SpeaKer[q])

      meanD <- mean(vTAgSub[,2])
        sdD <-   stats::sd(vTAgSub[,2])

      vTsub <- subset(vowelTab, vowelTab[,1]==SpeaKer[q])
      vTsub[,indexVowel+1] <- (vTsub[,indexVowel+1]-meanD)/sdD

      vT <- rbind(vT,vTsub)
    }
  }

  if ((replyNormal==" Lobanov") & (nVoWel> 1))
  {
    vT <- data.frame()
    vTAg <- stats::aggregate(vowelTab[,indexVowel+1]~vowelTab[,1]+vowelTab[,indexVowel], FUN=mean)

    for (q in (1:nSpeaKer))
    {
      vTAgSub <- subset(vTAg, vTAg[,1]==SpeaKer[q])

      meanD <- mean(vTAgSub[,3])
        sdD <-   stats::sd(vTAgSub[,3])

      vTsub <- subset(vowelTab, vowelTab[,1]==SpeaKer[q])
      vTsub[,indexVowel+1] <- (vTsub[,indexVowel+1]-meanD)/sdD

      vT <- rbind(vT,vTsub)
    }
  }

  return(vT)
}

################################################################################

speaker=NULL
indexColor=indexShape=indexPlot=NULL
X=Y=NULL
color=shape=vowel=plot=time=NULL
ll=ul=NULL
voweltime=NULL
xend=yend=NULL
V1=V2=NULL

################################################################################

visvow <- function()
{
  options(shiny.sanitize.errors = TRUE)
  options(shiny.usecairo=FALSE)
  options(shiny.maxRequestSize=200*1024^2)

  addResourcePath('www', system.file(package='visvow'))

  shinyApp(
    ui <- fluidPage(
      tags$style(type = 'text/css', '.title { margin-left: 20px; font-weight: bold; }'),
      tags$style(type = 'text/css', '.navbar-default > .container-fluid { margin-left: -11px; }'),
      tags$style(type = 'text/css', 'nav.navbar-default { margin-left: 15px; margin-right: 15px; }'),

      tags$style(type = 'text/css', 'p { margin-top: 0; margin-bottom: 0; }'),
      tags$style(type = 'text/css', 'h5 { margin-top: 0.3em; margin-bottom: 0.1em; }'),
      tags$style(type = 'text/css', 'h6 { margin-top: 1.0em; margin-bottom: 0.1em; }'),
      tags$style(type = 'text/css', 'li { margin-top: 0; margin-bottom: 0.4em; }'),

      tags$style(type = 'text/css', '.shiny-progress-container { position: absolute; left: 49% !important; width: 200px; margin-left: 100px; top: 40% !important; height: 100px; margin-top: 50px; z-index: 2000; }'),
      tags$style(type = 'text/css', '.shiny-progress .progress {position: absolute; left: 50% !important; width: 487px; margin-left: -419px; top: 50% !important; height: 16px; margin-top: 8px; }'),
      tags$style(type = 'text/css', '.shiny-progress .bar { background-color: #2081d4; .opacity = 0.8; }'),
      tags$style(type = 'text/css', '.shiny-progress .progress-text { position: absolute; right: 30px; height: 30px; width: 490px; background-color: #00AA00; padding-top: 2px; padding-right: 3px; padding-bottom: 2px; padding-left: 3px; opacity: 0.95; border-radius: 10px; -webkit-border-radius: 10px; -moz-border-radius: 10px; }'),
      tags$style(type = 'text/css', '.progress-text { top: 15px !important; color: #FFFFFF !important; text-align: center; }'),
      tags$style(type = 'text/css', '.shiny-progress .progress-text .progress-message { padding-top: 0px; padding-right: 3px; padding-bottom: 3px; padding-left: 10px; font-weight: bold; font-size: 18px; }'),
      tags$style(type = 'text/css', '.shiny-progress .progress-text .progress-detail { padding-top: 0px; padding-right: 3px; padding-bottom: 3px; padding-left: 3px; font-size: 17px; }'),

      tags$style(type = 'text/css', '#heartbeat { width: 0; height: 0; visibility: hidden; }'),

      img(src = 'www/FA1.png', height = 39, align = "right", style = 'margin-top: 19px; margin-right: 15px;'),
      titlePanel(title = HTML("<div class='title'>Visible Vowels<div>"), windowTitle = "Visible Vowels"),

      tags$head(
        tags$link(rel="icon", href="www/FA2.png"),

        tags$meta(charset="UTF-8"),
        tags$meta(name   ="description", content="Visible Vowels is a web app for the analysis of acoustic vowel measurements: f0, formants and duration. The app is an useful instrument for research in phonetics, sociolinguistics, dialectology, forensic linguistics, and speech-language pathology."),
      ),

      navbarPage
      (
        title=NULL, id = "navBar", collapsible = TRUE,

        tabPanel
        (
          title = "Load file",
          value = "load_file",

          fluidPage
          (
            style = "border: 1px solid silver; padding: 6px; min-height: 690px;",

            fluidPage
            (
              fileInput('vowelFile', 'Upload xlsx file', accept = c(".xlsx"), width="40%")
            ),

            fluidPage
            (
              style = "font-size: 90%; white-space: nowrap;",
              align = "center",
              DT::dataTableOutput('vowelRound')
            )
          )
        ),

        tabPanel
        (
          title = "Contours",
          value = "contours",

          splitLayout
          (
            style = "border: 1px solid silver;",
            cellWidths = c("32%", "68%"),
            cellArgs = list(style = "padding: 6px"),

            column
            (
              width=12,

              textInput("title0", "Plot title", "", width="100%"),
              uiOutput('selScale0'),
              uiOutput('selRef0'),

              splitLayout
              (
                cellWidths = c("70%", "30%"),

                radioButtons("selError0", "Size of confidence intervals:", c("0%","90%","95%","99%"), selected = "0%", inline = TRUE),
                radioButtons("selMeasure0", "Use:", c("SD","SE"), selected = "SE", inline = TRUE)
              ),

              splitLayout
              (
                uiOutput('selVar0'),
                uiOutput('selLine0'),
                uiOutput('selPlot0')
              ),

              splitLayout
              (
                uiOutput('catXaxis0'),
                uiOutput('catLine0'),
                uiOutput('catPlot0')
              ),

              checkboxGroupInput("selGeon0", "Options:", c("average", "points", "smooth"), selected="points", inline=TRUE)
            ),

            column
            (
              width = 12,

              uiOutput("Graph0"),

              column
              (
                width = 11,

                splitLayout
                (
                  uiOutput('selFormat0a'),
                  downloadButton('download0a', 'Table'),

                  uiOutput('selSize0b'),
                  uiOutput('selFont0b'),
                  uiOutput('selPoint0b'),
                  uiOutput('selFormat0b'),
                  downloadButton('download0b', 'Graph')
                )
              )
            )
          )
        ),

        tabPanel
        (
          title = "Formants",
          value = "formants",

          splitLayout
          (
            style = "border: 1px solid silver;",
            cellWidths = c("32%", "68%"),
            cellArgs = list(style = "padding: 6px"),

            column
            (
              width=12,

              textInput("title1", "Plot title", "", width="100%"),
              uiOutput('selTimes1'),

              splitLayout
              (
                cellWidths = c("50%", "50%"),
                uiOutput('selScale1'),
                uiOutput('selNormal1')
              ),

              uiOutput('selTimesN'),

              splitLayout
              (
                uiOutput('selColor1'),
                uiOutput('selShape1'),
                uiOutput('selPlot1')
              ),

              splitLayout
              (
                uiOutput('catColor1'),
                uiOutput('catShape1'),
                uiOutput('catPlot1')
              ),

              uiOutput('selGeon1'),
              uiOutput('selPars' ),

              splitLayout
              (
                cellWidths = c("28%", "26%", "46%"),
                checkboxInput("grayscale1", "grayscale"         , FALSE),
                checkboxInput("average1"  , "average"           , FALSE),
                checkboxInput("ltf1"      , "long-term formants", FALSE)
              )
            ),

            column
            (
              width=12,

              splitLayout
              (
                cellWidths = c("85%", "15%"),

                uiOutput("Graph1"),

                column
                (
                  width=12,

                  br(),br(),

                  selectInput('axisX', "x-axis", choices=c("F1","F2","F3","--"), selected="F2", selectize=FALSE, width = "100%"),
                  selectInput('axisY', "y-axis", choices=c("F1","F2","F3","--"), selected="F1", selectize=FALSE, width = "100%"),
                  selectInput('axisZ', "z-axis", choices=c("F1","F2","F3","--"), selected="--", selectize=FALSE, width = "100%"),

                  uiOutput('manScale'),
                  uiOutput('selF1min'),
                  uiOutput('selF1max'),
                  uiOutput('selF2min'),
                  uiOutput('selF2max')
                )
              ),

              column
              (
                width = 11,

                splitLayout
                (
                  uiOutput('selFormat1a'),
                  downloadButton('download1a', 'Table'),

                  uiOutput('selSize1b'),
                  uiOutput('selFont1b'),
                  uiOutput('selPoint1b'),
                  uiOutput('selFormat1b'),
                  downloadButton('download1b', 'Graph')
                )
              )
            )
          )
        ),

        tabPanel
        (
          title = "Dynamics",
          value = "dynamics",

          splitLayout
          (
            style = "border: 1px solid silver;",
            cellWidths = c("32%", "68%"),
            cellArgs = list(style = "padding: 6px"),

            column
            (
              width=12,

              textInput("title4", "Plot title", "", width="100%"),

              splitLayout
              (
                cellWidths = c("50%", "49%"),
                uiOutput('selScale4'),
                uiOutput('selMethod4')
              ),

              uiOutput('selGraph4'),

              splitLayout
              (
                cellWidths = c("70%", "30%"),

                radioButtons("selError4", "Size of confidence intervals:", c("0%","90%","95%","99%"), selected = "95%", inline = TRUE),
                radioButtons("selMeasure4", "Use:", c("SD","SE"), selected = "SE", inline = TRUE)
              ),

              splitLayout
              (
                cellWidths = c("21%", "26%", "26%", "26%"),
                uiOutput('selVar4'),
                uiOutput('selXaxis4'),
                uiOutput('selLine4'),
                uiOutput('selPlot4')
              ),

              splitLayout
              (
                cellWidths = c("21%", "26%", "26%", "26%"),
                uiOutput('selTimes4'),
                uiOutput('catXaxis4'),
                uiOutput('catLine4'),
                uiOutput('catPlot4')
              ),

              checkboxGroupInput("selGeon4", "Options:", c("average", "rotate x-axis labels"), inline=TRUE)
            ),

            column
            (
              width = 12,

              uiOutput("Graph4"),

              column
              (
                width = 11,

                splitLayout
                (
                  uiOutput('selFormat4a'),
                  downloadButton('download4a', 'Table'),

                  uiOutput('selSize4b'),
                  uiOutput('selFont4b'),
                  uiOutput('selPoint4b'),
                  uiOutput('selFormat4b'),
                  downloadButton('download4b', 'Graph')
                )
              )
            )
          )
        ),

        tabPanel
        (
          title = "Duration",
          value = "duration",

          splitLayout
          (
            style = "border: 1px solid silver;",
            cellWidths = c("32%", "68%"),
            cellArgs = list(style = "padding: 6px"),

            column
            (
              width=12,

              textInput("title2", "Plot title", "", width="100%"),
              uiOutput('selNormal2'),
              uiOutput('selGraph2'),

              splitLayout
              (
                cellWidths = c("70%", "30%"),

                radioButtons("selError2", "Size of confidence intervals:", c("0%","90%","95%","99%"), selected = "95%", inline = TRUE),
                radioButtons("selMeasure2", "Use:", c("SD","SE"), selected = "SE", inline = TRUE)
              ),

              splitLayout
              (
                uiOutput('selXaxis2'),
                uiOutput('selLine2'),
                uiOutput('selPlot2')
              ),

              splitLayout
              (
                uiOutput('catXaxis2'),
                uiOutput('catLine2'),
                uiOutput('catPlot2')
              ),

              checkboxGroupInput("selGeon2", "Options:", c("average", "rotate x-axis labels"), inline=TRUE)
            ),

            column
            (
              width = 12,

              uiOutput("Graph2"),

              column
              (
                width = 11,

                splitLayout
                (
                  uiOutput('selFormat2a'),
                  downloadButton('download2a', 'Table'),

                  uiOutput('selSize2b'),
                  uiOutput('selFont2b'),
                  uiOutput('selPoint2b'),
                  uiOutput('selFormat2b'),
                  downloadButton('download2b', 'Graph')
                )
              )
            )
          )
        ),

        tabPanel
        (
          title = "Explore",
          value = "explore",

          splitLayout
          (
            style = "border: 1px solid silver;",
            cellWidths = c("32%", "68%"),
            cellArgs = list(style = "padding: 6px"),

            column
            (
              width=12,

              textInput("title3", "Plot title", "", width="100%"),
              uiOutput('selTimes3'),

              splitLayout
              (
                cellWidths = c("50%", "50%"),
                checkboxGroupInput("selFormant3", "Include formants:", c("F1","F2","F3"), selected=c("F1","F2"), TRUE),
                radioButtons('selMetric3', 'Metric:', c("Euclidean","Accdist"), selected = "Euclidean", TRUE)
              ),

              splitLayout
              (
                cellWidths = c("50%", "50%"),
                uiOutput('selScale3'),
                uiOutput('selNormal3')
              ),

              uiOutput('selTimesN3'),

              splitLayout
              (
                uiOutput('selVowel3'),
                uiOutput('selGrouping3'),
                uiOutput('catGrouping3')
              ),

              radioButtons('selClass3', 'Explorative method:', c("Cluster analysis","Multidimensional scaling"), selected = "Cluster analysis", TRUE),
              uiOutput('selMethod3'),
              uiOutput('explVar3'),

              uiOutput("selGeon3"),

              splitLayout
              (
                checkboxInput("grayscale3", "grayscale", FALSE),
                checkboxInput("summarize3" ,"summarize", FALSE)
              )
            ),

            column
            (
              width = 12,

              uiOutput("Graph3"),

              column
              (
                width = 11,

                splitLayout
                (
                  uiOutput('selFormat3a'),
                  downloadButton('download3a', 'Table'),

                  uiOutput('selSize3b'),
                  uiOutput('selFont3b'),
                  uiOutput('selPoint3b'),
                  uiOutput('selFormat3b'),
                  downloadButton('download3b', 'Graph')
                )
              )
            )
          )
        ),

        tabPanel
        (
          title = "Evaluate",
          value = "evaluate",

          splitLayout
          (
            style = "border: 1px solid silver; min-height: 690px;",
            cellWidths = c("32%", "68%"),
            cellArgs = list(style = "padding: 6px"),

            column
            (
              width=12,

              fluidPage(
                style = 'border: 1px solid silver; margin-top: 7px; padding-top: 4px; padding-bottom: 4px;',
                align = "center",
                actionLink("buttonHelp5", label="", icon=icon("info-circle", lib = "font-awesome"), style='color: #2c84d7; font-size: 180%; margin-left: 4px;')
              ),

              br(),

              uiOutput('selTimes5'),
              uiOutput('selTimesN5'),

              splitLayout(
                uiOutput('selVars51'),
                uiOutput('selVars52')
              ),

              uiOutput("selF035"),

              br(),

              uiOutput('goButton')
            ),

            column
            (
              width = 12,
              uiOutput("Graph5"),

              hr(style='border-top: 1px solid #cccccc;'),

              splitLayout
              (
                radioButtons(inputId  = 'selMeth5',
                             label    = 'Choose:',
                             choices  = c("Evaluate",
                                          "Compare"),
                             selected =   "Evaluate",
                             inline   = FALSE),

                uiOutput("getOpts5"),
                uiOutput("getEval5")
              )
            )
          )
        ),

        navbarMenu("More",

        tabPanel
        (
          title = "Help",
          value = "help",

          fluidPage
          (
            style = "border: 1px solid silver;",

            br(),
            h5(strong("About")),
            p("Visible Vowels is a web app for the analysis of acoustic vowel measurements: f0, formants and duration. The app is an useful instrument for research in phonetics, sociolinguistics, dialectology, forensic linguistics, and speech-language pathology. The following people were involved in the development of Visible Vowels: Wilbert Heeringa (implementation), Hans Van de Velde (project manager), Vincent van Heuven (advice). Visible Vowels is still under development. Comments are welcome and can be sent to", img(src = 'www/email.png', height = 20, align = "center"),"."),
            br(),
            h5(strong("System requirements")),
            p("Visible Vowels runs best on a computer with a monitor with a minimum resolution of 1370 x 870 (width x height). The use of Mozilla Firefox as a web browser is to be preferred."),
            br(),
            h5(strong("Format")),
            p("The input file should be a spreadsheet that is created in Excel or LibreOffice. It should be saved as an Excel 2007/2010/2013 XML file, i.e. with extension '.xlsx'. The spreadsheet should include the following variables (shown in red):"),
            br(),
            
            tags$div(tags$ul
            (
              tags$li(tags$span(HTML("<span style='font-variant: small-caps; color:blue'>General</span>"), div(tags$ul(
                tags$li(tags$span(HTML("<span style='color:crimson'>speaker</span>"), p("Contains the speaker labels. This column is obligatory."))),
                tags$li(tags$span(HTML("<span style='color:crimson'>vowel</span>"), p("Contains the vowel labels. Multiple pronunciations of the same vowel per speaker are possible. In case you want to use IPA characters, enter them as Unicode characters. In order to find Unicode IPA characters, use the online", tags$a(href="http://westonruter.github.io/ipa-chart/keyboard/", "IPA Chart Keyboard", target="_blank"), "of Weston Ruter. This column is obligatory."))),
                tags$li(tags$span(HTML("<span style='color:crimson'>timepoint</span>"), p("In this column the time points are labeled by numbers that indicate the order of the time points in the vowel interval. This column is obligatory only when using the long format.")))
              )))),
                     
              tags$li(tags$span(HTML("<span style='font-variant: small-caps; color:blue'>Sociolinguistic</span>"),div(tags$ul(
                tags$li(tags$span(HTML("<span style='color:crimson'>...</span>"), p("An arbitrary number of columns representing categorical variables such as location, language, gender, age group, etc. may follow, but is not obligatory. See to it that each categorical variable has an unique set of different values. Prevent the use of numbers, rather use meaningful codes. For example, rather then using codes '1' and '2' for a variable 'age group' use 'old' and 'young' or 'o' and 'y'.")))
              )))),
                       
              tags$li(tags$span(HTML("<span style='font-variant: small-caps; color:blue'>Vowel</span>"), div(tags$ul(
                tags$li(tags$span(HTML("<span style='color:crimson'>duration</span>"), p("Durations of the vowels. The measurements may be either in seconds or milliseconds. This column is obligatory but may be kept empty."))),
                tags$li(tags$span(HTML("<span style='color:crimson'>time f0 F1 F2 F3</span>"), p("A set of five columns should follow: 'time', 'f0', 'F1', 'F2' and 'F3'. The variable 'time' gives the time point at which f0, F1, F2 and F3 are measured. This time point within the vowel interval should be measured in seconds or milliseconds. It is assumed that the vowel interval starts at 0 (milli)seconds. It is assumed that f0, F1, F2 and F3 are measured in Hertz and not normalized. A set should always include all five columns, but the columns 'time', 'f0' and 'F3' may be kept empty. ", em("As many"), " sets can be included as time points within the vowel interval are chosen. But a set should occur at least one time. When using the wide format, all the sets are found in the same row, and for each set the same column names should be used. When using the long format, each set is found in a seperate row, and rows that refer to the same realization are distinguished by the codes in the 'timepoint' column.")))
              ))))
            )),
            
            br(),
            p("Below both the wide and the long format are schematically shown by means of an example. In this example there are three speakers labeled as 'A', 'B' and 'C'. Each of the speakers pronounced two different vowels: i: and \u0254. Each vowel has been pronounced twice by each speaker, and for each realization f0, F1, F2 and F3 are measured at two time points."),
            br(),
            p("Note the importance of the numbers in the fourth column in the long table, where they make it clear which measurements at multiple time points relate to the same vowel realization. In fact, the long format requires that all cases in the table be uniquely defined by the combination of the 'speaker' variable, the 'vowel' variable, the 'timepoint' variable and the categorical variables that follow, i.e. the pink, yellow, grey and white columns to the left of the 'duration' variable."),
            br(),
            p(em("Wide format"), style="margin-left: 41px;"),
            br(),
            div(img(src = 'www/format1.png', width=580), style="margin-left: 41px;"),
            br(), br(),
            p(em("Long format"), style="margin-left: 41px;"),
            br(),
            div(img(src = 'www/format2.png', width=450), style="margin-left: 41px;"),
            br(), br(),

            h5(strong("Example input file")),
            p("In order to try Visible Vowels an example spreadsheet can be downloaded ", a("here", href = "www/example.xlsx", target = "_blank"), "and be loaded by this program."),
            br(),
            h5(strong("Graphs")),
            p("Graphs can be saved in six formats: JPG, PNG, SVG, EPS, PDF and TEX. TEX files are created with TikZ. When using this format, it is assumed that XeLaTeX is installed. Generating a TikZ may take a long time. When including a TikZ file in a LaTeX document, you need to use a font that supports the IPA Unicode characters, for example: 'Doulos SIL', 'Charis SIL' or 'Linux Libertine O'. You also need to adjust the left margin and the scaling of the graph. The LaTeX document should be compiled with", code("xelatex"), ". Example of a LaTeX file in which a TikZ file is included:"),
            br(),
        
            code(style="margin-left: 36px;", "\\documentclass{minimal}"),
            br(), br(),
            code(style="margin-left: 36px;", "\\usepackage{tikz}"),
            br(),
            code(style="margin-left: 36px;", "\\usepackage{fontspec}"),
            br(),
            code(style="margin-left: 36px;", "\\setmainfont{Linux Libertine O}"),
            br(), br(),
            code(style="margin-left: 36px;", "\\begin{document}"),
            br(),
            code(style="margin-left: 36px;", "{\\hspace*{-3cm}\\scalebox{0.8}{\\input{formantPlot.TEX}}}"),
            br(),
            code(style="margin-left: 36px;", "\\end{document}"),
            br(), br(), br(),
            
            h5(strong("Implementation")),
            p("This program is implemented as a Shiny app. Shiny was developed by RStudio. This app uses the following R packages:"),
            br(),

            tags$div(tags$ul
            (
              tags$li(tags$span(HTML("<span style='color:blue'>base</span>"),p("R Core Team (2017). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. https://www.R-project.org/"))),
              tags$li(tags$span(HTML("<span style='color:blue'>shiny</span>"),p("Winston Chang, Joe Cheng, J.J. Allaire, Yihui Xie and Jonathan McPherson (2017). shiny: Web Application Framework for R. R package version 1.0.0. https://CRAN.R-project.org/package=shiny"))),
              tags$li(tags$span(HTML("<span style='color:blue'>shinyBS</span>"),p("Eric Bailey (2015). shinyBS: Twitter Bootstrap Components for Shiny. R package version 0.61. https://CRAN.R-project.org/package=shinyBS"))),
              tags$li(tags$span(HTML("<span style='color:blue'>stats</span>"),p("R Core Team (2017). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. https://www.R-project.org/"))),
              tags$li(tags$span(HTML("<span style='color:blue'>tydr</span>"),p("Hadley Wickham and Lionel Henry (2019). tidyr: Tidy Messy Data. R package version 1.0.0. https://CRAN.R-project.org/package=tidyr"))),
              tags$li(tags$span(HTML("<span style='color:blue'>PBSmapping</span>"),p("Jon T. Schnute, Nicholas Boers and Rowan Haigh (2019). PBSmapping: Mapping Fisheries Data and Spatial Analysis Tools. R package version 2.72.1. https://CRAN.R-project.org/package=PBSmapping"))),
              tags$li(tags$span(HTML("<span style='color:blue'>splitstackshape</span>"),p("Ananda Mahto (2019). splitstackshape: Stack and Reshape Datasets After Splitting Concatenated Values. R package version 1.4.8. https://CRAN.R-project.org/package=splitstackshape"))),
              tags$li(tags$span(HTML("<span style='color:blue'>plyr</span>"),p("Hadley Wickham (2011). The Split-Apply-Combine Strategy for Data Analysis. Journal of Statistical Software, 40(1), 1-29. URL http://www.jstatsoft.org/v40/i01/"))),
              tags$li(tags$span(HTML("<span style='color:blue'>dplyr</span>"),p("Hadley Wickham, Romain Fran\u00E7ois, Lionel Henry and Kirill M\u00FCller (2022). dplyr: A Grammar of Data Manipulation. R package version 1.0.10. https://CRAN.R-project.org/package=dplyr"))),
              tags$li(tags$span(HTML("<span style='color:blue'>formattable</span>"),p("Kun Ren and Kenton Russell (2016). formattable: Create 'Formattable' Data Structures. R package version 0.2.0.1. https://CRAN.R-project.org/package=formattable"))),
              tags$li(tags$span(HTML("<span style='color:blue'>ggplot2</span>"),p("H. Wickham (2009). ggplot2: Elegant Graphics for Data Analysis. Springer-Verlag New York. http://ggplot2.org"))),
              tags$li(tags$span(HTML("<span style='color:blue'>plot3D</span>"),p("Karline Soetaert (2017). plot3D: Plotting Multi-Dimensional Data. R package version 1.1.1. https://CRAN.R-project.org/package=plot3D"))),
              tags$li(tags$span(HTML("<span style='color:blue'>MASS</span>"),p("W.N. Venables & B.D. Ripley (2002). Modern Applied Statistics with S. Fourth Edition. Springer, New York. ISBN 0-387-95457-0"))),
              tags$li(tags$span(HTML("<span style='color:blue'>ggdendro</span>"),p("Andrie de Vries and Brian D. Ripley (2016). ggdendro: Create Dendrograms and Tree Diagrams Using 'ggplot2'. R package version 0.1-20. https://CRAN.R-project.org/package=ggdendro"))),
              tags$li(tags$span(HTML("<span style='color:blue'>ggrepel</span>"),p("Kamil Slowikowski (2017). ggrepel: Repulsive Text and Label Geoms for 'ggplot2'. R package version 0.7.0. https://CRAN.R-project.org/package=ggrepel"))),
              tags$li(tags$span(HTML("<span style='color:blue'>readxl</span>"),p("Hadley Wickham and Jennifer Bryan (2017). readxl: Read Excel Files. R package version 1.0.0. https://CRAN.R-project.org/package=readxl"))),
              tags$li(tags$span(HTML("<span style='color:blue'>WriteXLS</span>"),p("Marc Schwartz and various authors. (2015). WriteXLS: Cross-Platform Perl Based R Function to Create Excel 2003 (XLS) and Excel 2007 (XLSX) Files. R package version 4.0.0. https://CRAN.R-project.org/package=WriteXLS"))),
              tags$li(tags$span(HTML("<span style='color:blue'>DT</span>"),p("Yihui Xie (2016). DT: A Wrapper of the JavaScript Library 'DataTables'. R package version 0.2. https://CRAN.R-project.org/package=DT"))),
              tags$li(tags$span(HTML("<span style='color:blue'>psych</span>"),p("William Revelle (2016). psych: Procedures for Personality and Psychological Research, Northwestern University, Evanston, Illinois, USA, Version = 1.6.12, https://CRAN.R-project.org/package=psych"))),
              tags$li(tags$span(HTML("<span style='color:blue'>pracma</span>"),p("Hans Werner Borchers (2017). pracma: Practical Numerical Math Functions. R package version 1.9.9. https://CRAN.R-project.org/package=pracma"))),
              tags$li(tags$span(HTML("<span style='color:blue'>Rtsne</span>"),p("Jesse H. Krijthe (2015). Rtsne: T-Distributed Stochastic Neighbor Embedding using a Barnes-Hut Implementation. https://github.com/jkrijthe/Rtsne"),p("L.J.P. van der Maaten and G.E. Hinton (2008). Visualizing High-Dimensional Data Using t-SNE. Journal of Machine Learning Research 9(Nov):2579-2605"),p("L.J.P. van der Maaten (2014). Accelerating t-SNE using Tree-Based Algorithms. Journal of Machine Learning Research 15(Oct):3221-3245"))),
              tags$li(tags$span(HTML("<span style='color:blue'>grid</span>"),p("R Core Team (2017). R: A language and environment for statistical computing. R Foundation for Statistical Computing, Vienna, Austria. https://www.R-project.org/"))),
              tags$li(tags$span(HTML("<span style='color:blue'>svglite</span>"),p("Hadley Wickham, Lionel Henry, T Jake Luciani, Matthieu Decorde and Vaudor Lise (2016). svglite: An 'SVG' Graphics Device. R package version 1.2.0. https://CRAN.R-project.org/package=svglite"))),
              tags$li(tags$span(HTML("<span style='color:blue'>Cairo</span>"),p("Simon Urbanek and Jeffrey Horner (2015). Cairo: R graphics device using cairo graphics library for creating high-quality bitmap (PNG, JPEG, TIFF),  vector (PDF, SVG, PostScript) and display (X11 and Win32) output. R package version 1.5-9. https://CRAN.R-project.org/package=Cairo"))),
              tags$li(tags$span(HTML("<span style='color:blue'>tikzDevice</span>"),p("Charlie Sharpsteen and Cameron Bracken (2020). tikzDevice: R Graphics Output in LaTeX Format. R package version 0.12.3.1. https://CRAN.R-project.org/package=tikzDevice"))),
              tags$li(tags$span(HTML("<span style='color:blue'>shinybusy</span>"),p("Fanny Meyer and Victor Perrier (2020). shinybusy: Busy Indicator for 'Shiny' Applications. R package version 0.2.2. https://CRAN.R-project.org/package=shinybusy")))
            )),

            br(),
            p("Visible Vowels allows to convert and normalize vowel data and calculate some specific metrics. To get all the details on how these values are calculated, type ", span(style="font-family: monospace; font-size: 100%;", 'vignette("visvow")'), " in the R console."),
            br(),
            h5(strong("How to cite this app")),
            p("Heeringa, W. & Van de Velde, H. (2018). \u201CVisible Vowels: a Tool for the Visualization of Vowel Variation.\u201D In ",tags$i("Proceedings CLARIN Annual Conference 2018, 8 - 10 October, Pisa, Italy."),"CLARIN ERIC."),
            br()
          ),

          br()
        ),

        tabPanel
        (
          title = "Disclaimer",
          value = "disclaimer",

          fluidPage
          (
            style = "border: 1px solid silver; min-height: 690px;",

            br(),
            h5(strong("Liability")),
            p("This app is provided 'as is' without warranty of any kind, either express or implied, including, but not limited to, the implied warranties of fitness for a purpose, or the warranty of non-infringement. Without limiting the foregoing, the Fryske Akademy makes no warranty that: 1) the app will meet your requirements, 2) the app will be uninterrupted, timely, secure or error-free, 3) the results that may be obtained from the use of the app will be effective, accurate or reliable, 4) the quality of the app will meet your expectations, 5) any errors in the app will be corrected."),
            br(),
            p("The app and its documentation could include technical or other mistakes, inaccuracies or typographical errors. The Fryske Akademy may make changes to the app or documentation made available on its web site. The app and its documentation may be out of date, and the Fryske Akademy makes no commitment to update such materials."),
            br(),
            p("The Fryske Akademy assumes no responsibility for errors or ommissions in the app or documentation available from its web site."),
            br(),
            p("In no event shall the Fryske Akademy be liable to you or any third parties for any special, punitive, incidental, indirect or consequential damages of any kind, or any damages whatsoever, including, without limitation, those resulting from loss of use, data or profits, whether or not the Fryske Akademy has been advised of the possibility of such damages, and on any theory of liability, arising out of or in connection with the use of this software."),
            br(),
            p("The use of the app is done at your own discretion and risk and with agreement that you will be solely responsible for any damage to your computer system or loss of data that results from such activities. No advice or information, whether oral or written, obtained by you from the Fryske Akademy shall create any warranty for the software."),
            br(),
            h5(strong("Other")),
            p("The disclaimer may be changed from time to time."),
            br()
          )
        ))
      ),
      
      tags$td(textOutput("heartbeat"))
    ),

    ############################################################################

    server <- function(input, output, session)
    {
      observeEvent(input$navBar,
      {
        if (getUrlHash() == paste0("#", input$navBar)) return()
        updateQueryString(paste0("#", input$navBar), mode = "push")
      })

      observeEvent(getUrlHash(),
      {
        Hash <- getUrlHash()
        if (Hash == paste0("#", input$navBar)) return()
        Hash <- gsub("#", "", Hash)
        updateNavbarPage(session, "navBar", selected=Hash)
      })
      
      output$heartbeat <- renderText(
      {
        invalidateLater(5000)
        Sys.time()
      })

      observe({
        message("Press ESC once or more to exit, ignore warnings")
        options(warn = -1)
      })
      
      ##########################################################################

      vowelFile <- reactive(
      {
        inFile <- input$vowelFile

        if (is.null(inFile))
          return(NULL)

        file.rename(inFile$datapath, paste0(inFile$datapath,".xlsx"))
        vT <- as.data.frame(read_excel(paste0(inFile$datapath,".xlsx"), sheet=1, .name_repair = "minimal"))

        # wide format
        if (!("timepoint" %in% colnames(vT)))
        {
          return(vT)
        }  

        # long format
        if   ("timepoint" %in% colnames(vT))
        {
          timepoint <- vT$timepoint
          vT$timepoint <- NULL
          
          indexDuration <- grep("^duration$", tolower(colnames(vT)))
          ngroups <- (ncol(vT) - indexDuration) / 5
          
          if (ngroups == 1)
          {
            freq <- data.frame(table(timepoint))
            
            if (mean(freq$Freq) != round(mean(freq$Freq)))
            {
              showNotification("The number of time points is not the same for all cases!", type = "error", duration = 10)
              return(NULL)
            }
            
            if (!unique((sort(freq$timepoint) == 1:length(freq$timepoint))))
            {
              showNotification("Numbering of time points is not correct!", type = "error", duration = 10)
              return(NULL)
            }
            
            superIndex <- ""
            
            for (i in 1:(indexDuration-1))
            {
              superIndex <- paste0(superIndex, vT[,i])
            }
            
            ntimepoints <- nrow(freq)
            
            if ((nrow(vT)/ntimepoints) > length(unique(superIndex)))
            {
              showNotification("Not all cases are uniquely defined!", type = "error", duration = 10)
              return(NULL)
            }
            
            vT <- vT[order(superIndex, timepoint),]

            df <- dplyr::filter(vT, ((dplyr::row_number() %% ntimepoints)) == 1)
            df <- df[, 1:indexDuration]
            
            for (g in (1:ntimepoints))
            {
              if (g==ntimepoints) g0 <- 0 else g0 <- g
              
              df0 <- dplyr::filter(vT, ((dplyr::row_number() %% ntimepoints)) == g0)
              df0 <- df0[, (indexDuration+1):(indexDuration+5)]
              
              df <- cbind(df, df0)
            }
            
            return(df)
          }
          else
          {
            showNotification("Cannot have both a variable timepoint and multiple time points in one row!", type = "error", duration = 10)
            return(NULL)
          }
        }
      })

      checkVar <- function(varName, varIndex, checkEmpty)
      {
        indexVar <- grep(paste0("^", varName, "$"), tolower(colnames(vowelFile())))
        
        if (!is.element(tolower(varName), tolower(colnames(vowelFile()))))
          Message <- paste0("Column '", varName, "' not found.")
        else
          
        if ((varIndex!=0) && (tolower(colnames(vowelFile())[varIndex])!=tolower(varName)))
          Message <- paste0("'", varName, "' found in the wrong column.")
        else
            
        if (checkEmpty && (sum(is.na(vowelFile()[,indexVar]))==nrow(vowelFile())))
          Message <- paste0("Column '", varName, "' is empty.")
        else
          Message <- "OK"          
            
        return(Message)
      }
      
      checkVars <- reactive(
      {
        if (is.null(vowelFile()))
          return(NULL)
          
        m <- checkVar("speaker" , 1, T)
        if (m!="OK") return(m)
          
        m <- checkVar("vowel"   , 2, T)
        if (m!="OK") return(m)
          
        m <- checkVar("duration", 0, F)
        if (m!="OK") return(m)
          
        m <- checkVar("time"    , 0, F)
        if (m!="OK") return(m)
          
        m <- checkVar("f0"      , 0, F)
        if (m!="OK") return(m)
          
        m <- checkVar("F1"      , 0, T)
        if (m!="OK") return(m)
          
        m <- checkVar("F2"      , 0, T)
        if (m!="OK") return(m)
          
        m <- checkVar("F3"      , 0, F)
        if (m!="OK") return(m)
          
        return("OK")
      })
      
      Round <- function(x)
      {
        return(trunc(x+0.5))
      }

      vowelRound <- reactive(
      {
        if (is.null(vowelFile()))
          return(NULL)

        # check
        if (checkVars()!="OK")
        {
          showNotification(checkVars(), type = "error", duration = 10)
          return(NULL)
        }

        vT <- vowelFile()

        nColumns <- ncol(vT)

        for (i in (1:nColumns))
        {
          if (grepl("^duration",tolower(colnames(vT)[i])) |
              grepl("^time"    ,tolower(colnames(vT)[i])) |
              grepl("^f0"      ,tolower(colnames(vT)[i])) |
              grepl("^F1"      ,toupper(colnames(vT)[i])) |
              grepl("^F2"      ,toupper(colnames(vT)[i])) |
              grepl("^F3"      ,toupper(colnames(vT)[i])))
          {
            if (is.character(vT[,i]))
            {
              vT[,i] <- as.numeric(vT[,i])
            }

            if (sum(is.na(vT[,i]))<nrow(vT))
            {
              if (max(vT[,i], na.rm = TRUE)<=1)
              {
                vT[,i] <- round(((Round(vT[,i]*1000))/1000),3)
              }
              else
              {
                vT[,i] <- Round(vT[,i])
              }
            }
          }
        }

        return(vT)
      })

      output$vowelRound <- DT::renderDataTable(expr = vowelRound(), options = list(scrollX = TRUE))

      vowelTab <- reactive(
      {
        if (is.null(vowelFile()) || (checkVars()!="OK"))
          return(NULL)

        vT <- vowelFile()

        indexDuration <- grep("^duration$", tolower(colnames(vT)))

        if (indexDuration > 3)
        {
          cnames <- colnames(vT)
          vT <- data.frame(vT[,1], vT[,3:(indexDuration-1)], vT[,2], vT[,indexDuration:ncol(vT)])
          cnames <- c(cnames[1],cnames[3:(indexDuration-1)],cnames[2],cnames[indexDuration:ncol(vT)])
          colnames(vT) <- cnames
        }
        else {}

        indexVowel <- grep("^vowel$", tolower(trimws(colnames(vT), "r")))

        for (k in (1:(indexVowel+1)))
        {
          colnames(vT)[k] <- tolower(trimws(colnames(vT)[k], "r"))
        }

        k0 <- 0
        for (k in ((indexVowel+2):(ncol(vT))))
        {
          k0 <- k0 +1

          if (((k0 %% 5)==1) | ((k0 %% 5)==2))
          {
            colnames(vT)[k] <- tolower(colnames(vT)[k])
          }
          else
          {
            colnames(vT)[k] <- toupper(colnames(vT)[k])
          }
        }

        if (length(indexVowel)>0)
        {
          for (i in ((indexVowel+1):(ncol(vT))))
          {
            if (is.character(vT[,i]))
            {
              vT[,i] <- as.numeric(vT[,i])
            }

            if (sum(is.na(vT[,i]))==nrow(vT))
            {
              vT[,i] <- 0
            }
          }

          vT <- vT[rowSums(is.na(vT)) == 0,]
        }

        return(vT)
      })

      vowelExcl <- reactive(
      {
        if (is.null(vowelTab()) || (nrow(vowelTab())==0))
          return(NULL)

        vowels   <- unique(vowelTab()$vowel)
        vowels0  <- unique(vowelTab()$vowel)
        speakers <- unique(vowelTab()$speaker)

        for (i in 1:length(speakers))
        {
          vTsub  <- subset(vowelTab(), speaker==speakers[i])
          vowels <- intersect(vowels,unique(vTsub$vowel))
        }

        return(setdiff(vowels0,vowels))
      })

      vowelSame <- reactive(
      {
        if (is.null(vowelTab()) || (nrow(vowelTab())==0))
          return(NULL)

        if (length(vowelExcl())==0)
          return(vowelTab())
        else
          return(subset(vowelTab(), !is.element(vowelTab()$vowel,vowelExcl())))
      })

      showExclVow <- function()
      {
        if (length(vowelExcl()) > 0)
        {
          vowels <- "Vowels excluded: "
    
          for (i in 1:length(vowelExcl()))
          {
            vowels <- paste(vowels, vowelExcl()[i])
          }
    
          showNotification(vowels, type = "error", duration = 30)    
        }
      }

      ##########################################################################

      vowelScale0 <- reactive(
      {
        if ((length(input$replyRef0)==0) || is.na(input$replyRef0))
          Ref <- 50
        else
          Ref <- input$replyRef0

        return(vowelScale(vowelTab(),input$replyScale0,Ref))
      })

      fuseCols <- function(vT,replyValue)
      {
        columns <- ""

        if (length(replyValue)>0)
        {
          for (i in (1:length(replyValue)))
          {
            indexValue <- grep(paste0("^",as.character(replyValue)[i],"$"), colnames(vT))

            if (i==1)
              columns <- paste0(columns,vT[,indexValue])
            else
              columns <- paste (columns,vT[,indexValue])
          }
        }

        return(columns)
      }

      getTimeCode <- reactive(
      {
        indexVowel   <- grep("^vowel$", colnames(vowelTab()))
        nColumns     <- ncol(vowelTab())
        nPoints      <- (nColumns - (indexVowel + 1))/5

        percentages <-FALSE

        if (sum(is.na(vowelTab()[,indexVowel + 1]))!=nrow(vowelTab()))
        {
          meanDuration <- mean(vowelTab()[,indexVowel+1])

          if (mean(vowelTab()[,indexVowel+2+((nPoints-1)*5)]) <= meanDuration)
          {
            if (meanDuration==0)
            {
              meanDuration <- 0.000001
            }

            timeLabel <- c()
            timeCode  <- c()

            for (i in (1:nPoints))
            {
              indexTime <- indexVowel + 2 + ((i-1)*5)

              timeLabel[i] <- (mean(vowelTab()[,indexTime])/meanDuration) * 100
              timeCode [i] <- i

              names(timeCode) <- as.character(round(timeLabel))
            }

            percentages <-TRUE
          }
        }

        if (percentages==FALSE)
        {
          timeCode <- seq(from=1, to=nPoints, by=1)
          names(timeCode) <- as.character(timeCode)
        }

        return(timeCode)
      })

      vowelSub0 <- reactive(
      {
        if (is.null(vowelScale0()) || (nrow(vowelScale0())==0) || (length(input$catXaxis0)==0))
          return(NULL)

        vT <- vowelScale0()

        vT$indexPlot <- fuseCols(vowelScale0(),input$replyPlot0)
        vT$indexLine <- fuseCols(vowelScale0(),input$replyLine0)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))

        nColumns   <- ncol(vowelTab())
        nPoints    <- (nColumns - (indexVowel + 1))/5

        xi <- as.numeric(input$catXaxis0)
        xn <- names(getTimeCode())[xi]

        if (input$replyVar0=="f0")
          varIndex <- 1
        else
        if (input$replyVar0=="F1")
          varIndex <- 2
        else
        if (input$replyVar0=="F2")
          varIndex <- 3
        else
        if (input$replyVar0=="F3")
          varIndex <- 4
        else
          return(NULL)

        if (input$selError0=="0%")
          z <- 0
        if (input$selError0=="90%")
          z <- 1.645
        if (input$selError0=="95%")
          z <- 1.96
        if (input$selError0=="99%")
          z <- 2.575

        if ((length(input$catPlot0)==0) && ((length(input$catLine0)==0) | (length(input$catLine0)>14)))
        {
          x <- c()
          y <- c()
          s <- c()
          v <- c()

          for (i in (1:nPoints))
          {
            if (is.element(i,xi))
            {
              ii <- which(xi==i)
              x <- as.numeric(as.character(c(x,rep(xn[ii],nrow(vT)))))

              indexTime <- indexVowel + 2 + ((i-1)*5)
              y <- c(y,vT[,indexTime+varIndex])
              s <- c(s,as.character(vT$speaker))
              v <- c(v,as.character(vT$vowel))
            }
          }

          vT0 <- data.frame(x,s,v,y)

          if (is.element("average",input$selGeon0))
          {
            vT0 <- stats::aggregate(y~x+s+v, data=vT0, FUN=mean)
            vT0 <- stats::aggregate(y~x+  v, data=vT0, FUN=mean)
          }

          ag    <- stats::aggregate(y~x, data=vT0, FUN=mean)
          ag$sd <- stats::aggregate(y~x, data=vT0, FUN=sd)[,2]
          ag$n  <- stats::aggregate(y~x, data=vT0, FUN=length)[,2]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure0=="SD")
          {
            ag$ll <- ag[,2] - z * ag$sd
            ag$ul <- ag[,2] + z * ag$sd
          }
          if (input$selMeasure0=="SE")
          {
            ag$ll <- ag[,2] - z * ag$se
            ag$ul <- ag[,2] + z * ag$se
          }

          colnames(ag)[1] <- "time"
          colnames(ag)[2] <- input$replyVar0

          return(ag)
        }
        else

        if ((length(input$catPlot0)>0) && ((length(input$catLine0)==0) | (length(input$catLine0)>14)))
        {
          vT <- subset(vT, is.element(vT$indexPlot,input$catPlot0))
          vT$indexPlot <- as.character(vT$indexPlot)

          if (nrow(vT)==0)
            return(data.frame())

          x <- c()
          y <- c()
          p <- c()
          s <- c()
          v <- c()

          for (i in (1:nPoints))
          {
            if (is.element(i,xi))
            {
              ii <- which(xi==i)
              x <- as.numeric(as.character(c(x,rep(xn[ii],nrow(vT)))))

              indexTime <- indexVowel + 2 + ((i-1)*5)
              y <- c(y,vT[,indexTime+varIndex])
              p <- c(p,vT$indexPlot)
              s <- c(s,as.character(vT$speaker))
              v <- c(v,as.character(vT$vowel))
            }
          }

          vT0 <- data.frame(x,p,s,v,y)

          if (is.element("average",input$selGeon0))
          {
            vT0 <- stats::aggregate(y~x+p+s+v, data=vT0, FUN=mean)
            vT0 <- stats::aggregate(y~x+p+  v, data=vT0, FUN=mean)
          }

          ag    <- stats::aggregate(y~x+p, data=vT0, FUN=mean)
          ag$sd <- stats::aggregate(y~x+p, data=vT0, FUN=sd)[,3]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(y~x+p, data=vT0, FUN=length)[,3]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure0=="SD")
          {
            ag$ll <- ag[,3] - z * ag$sd
            ag$ul <- ag[,3] + z * ag$sd
          }
          if (input$selMeasure0=="SE")
          {
            ag$ll <- ag[,3] - z * ag$se
            ag$ul <- ag[,3] + z * ag$se
          }

          ag <- ag[order(ag[,2]),]

          colnames(ag)[1] <- "time"
          colnames(ag)[2] <- paste(input$replyPlot0, collapse = " ")
          colnames(ag)[3] <- input$replyVar0

          return(ag)
        }
        else

        if ((length(input$catPlot0)==0) && ((length(input$catLine0)>0) & (length(input$catLine0)<=14)))
        {
          vT <- subset(vT, is.element(vT$indexLine,input$catLine0))
          vT$indexLine <- as.character(vT$indexLine)

          if (nrow(vT)==0)
            return(data.frame())

          x <- c()
          y <- c()
          l <- c()
          s <- c()
          v <- c()

          for (i in (1:nPoints))
          {
            if (is.element(i,xi))
            {
              ii <- which(xi==i)
              x <- as.numeric(as.character(c(x,rep(xn[ii],nrow(vT)))))

              indexTime <- indexVowel + 2 + ((i-1)*5)
              y <- c(y,vT[,indexTime+varIndex])
              l <- c(l,vT$indexLine)
              s <- c(s,as.character(vT$speaker))
              v <- c(v,as.character(vT$vowel))
            }
          }

          vT0 <- data.frame(x,l,s,v,y)

          if (is.element("average",input$selGeon0))
          {
            vT0 <- stats::aggregate(y~x+l+s+v, data=vT0, FUN=mean)
            vT0 <- stats::aggregate(y~x+l+  v, data=vT0, FUN=mean)
          }

          ag    <- stats::aggregate(y~x+l, data=vT0, FUN=mean)
          ag$sd <- stats::aggregate(y~x+l, data=vT0, FUN=sd)[,3]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(y~x+l, data=vT0, FUN=length)[,3]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure0=="SD")
          {
            ag$ll <- ag[,3] - z * ag$sd
            ag$ul <- ag[,3] + z * ag$sd
          }
          if (input$selMeasure0=="SE")
          {
            ag$ll <- ag[,3] - z * ag$se
            ag$ul <- ag[,3] + z * ag$se
          }

          ag <- ag[order(ag[,2]),]

          colnames(ag)[1] <- "time"
          colnames(ag)[2] <- paste(input$replyLine0, collapse = " ")
          colnames(ag)[3] <- input$replyVar0

          return(ag)
        }
        else

        if ((length(input$catPlot0)>0) && ((length(input$catLine0)>0) & (length(input$catLine0)<=14)))
        {
          vT <- subset(vT, is.element(vT$indexPlot,input$catPlot0) & is.element(vT$indexLine,input$catLine0))
          vT$indexPlot <- as.character(vT$indexPlot)
          vT$indexLine <- as.character(vT$indexLine)

          if (nrow(vT)==0)
            return(data.frame())

          x <- c()
          y <- c()
          p <- c()
          l <- c()
          s <- c()
          v <- c()

          for (i in (1:nPoints))
          {
            if (is.element(i,xi))
            {
              ii <- which(xi==i)
              x <- as.numeric(as.character(c(x,rep(xn[ii],nrow(vT)))))

              indexTime <- indexVowel + 2 + ((i-1)*5)
              y <- c(y,vT[,indexTime+varIndex])
              p <- c(p,vT$indexPlot)
              l <- c(l,vT$indexLine)
              s <- c(s,as.character(vT$speaker))
              v <- c(v,as.character(vT$vowel))
            }
          }

          vT0 <- data.frame(x,p,l,s,v,y)

          if (is.element("average",input$selGeon0))
          {
            vT0 <- stats::aggregate(y~x+p+l+s+v, data=vT0, FUN=mean)
            vT0 <- stats::aggregate(y~x+p+l+  v, data=vT0, FUN=mean)
          }

          ag    <- stats::aggregate(y~x+p+l, data=vT0, FUN=mean)
          ag$sd <- stats::aggregate(y~x+p+l, data=vT0, FUN=sd)[,4]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(y~x+p+l, data=vT0, FUN=length)[,4]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure0=="SD")
          {
            ag$ll <- ag[,4] - z * ag$sd
            ag$ul <- ag[,4] + z * ag$sd
          }
          if (input$selMeasure0=="SE")
          {
            ag$ll <- ag[,4] - z * ag$se
            ag$ul <- ag[,4] + z * ag$se
          }

          ag <- ag[order(ag[,2]),]

          colnames(ag)[1] <- "time"
          colnames(ag)[2] <- paste(input$replyPlot0, collapse = " ")
          colnames(ag)[3] <- paste(input$replyLine0, collapse = " ")
          colnames(ag)[4] <- input$replyVar0

          return(ag)
        }
        else
          return(data.frame())
      })

      output$selScale0 <- renderUI(
      {
        selectInput('replyScale0', 'Scale:', optionsScale(), selected = optionsScale()[1], selectize=FALSE, multiple=FALSE, width="100%")
      })

      output$selRef0 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if ((length(input$replyScale0)>0) && (input$replyScale0=="ST"))
          numericInput('replyRef0', 'Reference frequency:', value=50, min=1, step=1, width = "100%")
        else
          return(NULL)
      })

      output$selVar0 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        options <- c("f0","F1","F2","F3")
        selectInput('replyVar0', 'Variable:', options, selected = options[1], multiple=FALSE, selectize=FALSE, width="100%", size=4)
      })

      output$catXaxis0 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        selectInput('catXaxis0', 'Select points:', getTimeCode(), multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$selLine0 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[indexVowel]),colnames(vowelTab()[1:(indexVowel-1)]))

        selectInput('replyLine0', 'Color variable:', options, selected = options[1], multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$catLine0 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyLine0)>0)
          options <- unique(fuseCols(vowelTab(),input$replyLine0))
        else
          options <- NULL

        selectInput('catLine0', 'Select colors:', options, multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$selPlot0 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[indexVowel]),colnames(vowelTab()[1:(indexVowel-1)]))

        selectInput('replyPlot0', 'Panel variable:', options, selected = options[1], multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$catPlot0 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyPlot0)>0)
          options <- unique(fuseCols(vowelTab(),input$replyPlot0))
        else
          options <- NULL

        selectInput('catPlot0', 'Select panels:', options, multiple=TRUE, selectize=FALSE, width="100%")
      })

      scaleLab <- function(replyScale)
      {
        if (replyScale==" Hz")
          return("Hz")

        if (replyScale==" bark I")
          return("bark")

        if (replyScale==" bark II")
          return("bark")

        if (replyScale==" bark III")
          return("bark")

        if (replyScale==" ERB I")
          return("ERB")

        if (replyScale==" ERB II")
          return("ERB")

        if (replyScale==" ERB III")
          return("ERB")

        if (replyScale==" ln")
          return("ln")

        if (replyScale==" mel I")
          return("mel")

        if (replyScale==" mel II")
          return("mel")

        if (replyScale==" ST")
          return("ST")
      }

      scaleLab0 <- function()
      {
        return(scaleLab(input$replyScale0))
      }

      plotGraph0 <- function()
      {
        if (is.null(vowelSub0()) || (nrow(vowelSub0())==0))
          return(NULL)

        if ((length(input$catPlot0)==0) && ((length(input$catLine0)==0) | (length(input$catLine0)>14)))
        {
          vT <- data.frame(x=vowelSub0()$time, y=vowelSub0()[,2], ll=vowelSub0()$ll, ul=vowelSub0()$ul)

          if (!is.element("smooth", input$selGeon0))
            vS <- vT
          else
          {
            vS <- data.frame(stats::spline(vT$x, vT$y, n=nrow(vT)*10))
            vS$ll <- stats::spline(vT$x, vT$ll, n=nrow(vT)*10)$y
            vS$ul <- stats::spline(vT$x, vT$ul, n=nrow(vT)*10)$y
          }

          if (is.element("points", input$selGeon0))
            Geom_Point <- geom_point(colour="indianred2", size=3)
          else
            Geom_Point <- geom_point(colour="indianred2", size=0)

          graphics::plot(ggplot(data=vT, aes(x, y, group=1)) +
                         geom_line(data=vS, colour="indianred2", linewidth=1) +
                         Geom_Point +
                         geom_ribbon(data=vS, aes(ymin=ll, ymax=ul), alpha=0.2) +
                         ggtitle(input$title0) +
                         scale_x_continuous(breaks = unique(vT$x)) +
                         xlab("relative duration") + ylab(paste0(input$replyVar0," (",scaleLab0(),")")) +
                         theme_bw() +
                         theme(text           =element_text(size=as.numeric(input$replyPoint0b), family=input$replyFont0b),
                               plot.title     =element_text(face="bold", hjust = 0.5),
                               aspect.ratio   =0.67))
        }
        else

        if ((length(input$catPlot0)>0) && ((length(input$catLine0)==0) | (length(input$catLine0)>14)))
        {
          vT <- data.frame(x=vowelSub0()$time, y=vowelSub0()[,3], p=vowelSub0()[,2], ll=vowelSub0()$ll, ul=vowelSub0()$ul)

          if (!is.element("smooth", input$selGeon0))
            vS <- vT
          else
          {
            panels <- unique(vT$p)

            vS <- data.frame()
            for (i in 1:length(panels))
            {
              vSsub <- subset(vT, p==panels[i])
              vSspl <- data.frame(stats::spline(vSsub$x, vSsub$y, n=nrow(vSsub)*10), p=panels[i])
              vSspl$ll <- stats::spline(vSsub$x, vSsub$ll, n=nrow(vSsub)*10)$y
              vSspl$ul <- stats::spline(vSsub$x, vSsub$ul, n=nrow(vSsub)*10)$y
              vS <- rbind(vS,vSspl)
            }

            vT <- vT[with(vT, order(x, p)), ]
            vS <- vS[with(vS, order(x, p)), ]
          }

          if (is.element("points", input$selGeon0))
            Geom_Point <- geom_point(colour="indianred2", size=3)
          else
            Geom_Point <- geom_point(colour="indianred2", size=0)

          graphics::plot(ggplot(data=vT, aes(x, y, group=1)) +
                         geom_line(data=vS, colour="indianred2", linewidth=1) +
                         Geom_Point +
                         geom_ribbon(data=vS, aes(ymin=ll, ymax=ul), alpha=0.2) +
                         ggtitle(input$title0) +
                         scale_x_continuous(breaks = unique(vT$x)) +
                         xlab("relative duration") + ylab(paste0(input$replyVar0," (",scaleLab0(),")")) +
                         facet_wrap(vars(p)) +
                         theme_bw() +
                         theme(text           =element_text(size=as.numeric(input$replyPoint0b), family=input$replyFont0b),
                               plot.title     =element_text(face="bold", hjust = 0.5),
                               aspect.ratio   =0.67))
        }
        else

        if ((length(input$catPlot0)==0) && ((length(input$catLine0)>0) & (length(input$catLine0)<=14)))
        {
          vT <- data.frame(x=vowelSub0()$time, y=vowelSub0()[,3], l=vowelSub0()[,2], ll=vowelSub0()$ll, ul=vowelSub0()$ul)

          if (!is.element("smooth", input$selGeon0))
            vS <- vT
          else
          {
            lines <- unique(vT$l)

            vS <- data.frame()
            for (i in 1:length(lines))
            {
              vSsub <- subset(vT, l==lines[i])
              vSspl <- data.frame(stats::spline(vSsub$x, vSsub$y, n=nrow(vSsub)*10), l=lines[i])
              vSspl$ll <- stats::spline(vSsub$x, vSsub$ll, n=nrow(vSsub)*10)$y
              vSspl$ul <- stats::spline(vSsub$x, vSsub$ul, n=nrow(vSsub)*10)$y
              vS <- rbind(vS,vSspl)
            }

            vT <- vT[with(vT, order(x, l)), ]
            vS <- vS[with(vS, order(x, l)), ]
          }

          if (is.element("points", input$selGeon0))
            Geom_Point <- geom_point(size=3)
          else
            Geom_Point <- geom_point(size=0)

          graphics::plot(ggplot(data=vT, aes(x, y, group=l, color=l)) +
                         geom_line(data=vS, linewidth=1) +
                         Geom_Point +
                         geom_ribbon(data=vS, aes(x=x, ymin=ll, ymax=ul, fill = l), alpha=0.2, colour=NA) +
                         ggtitle(input$title0) +
                         scale_x_continuous(breaks = unique(vT$x)) +
                         xlab("relative duration") + ylab(paste0(input$replyVar0," (",scaleLab0(),")")) +
                         scale_colour_discrete(name=paste0(paste(input$replyLine0,collapse = " "),"\n")) +
                         theme_bw() +
                         theme(text           =element_text(size=as.numeric(input$replyPoint0b), family=input$replyFont0b),
                               plot.title     =element_text(face="bold", hjust = 0.5),
                               legend.key.size=unit(1.5, 'lines'),
                               aspect.ratio   =0.67) +
                         guides(fill="none"))
        }
        else

        if ((length(input$catPlot0)>0) && ((length(input$catLine0)>0) & (length(input$catLine0)<=14)))
        {
          vT <- data.frame(x=vowelSub0()$time, y=vowelSub0()[,4], p=vowelSub0()[,2], l=vowelSub0()[,3], ll=vowelSub0()$ll, ul=vowelSub0()$ul)

          if (!is.element("smooth", input$selGeon0))
            vS <- vT
          else
          {
            panels <- unique(vT$p)
             lines <- unique(vT$l)

            vS <- data.frame()
            for (i in 1:length(panels))
            {
              for (j in 1:length(lines))
              {
                vSsub <- subset(vT, (p==panels[i]) & (l==lines[j]))

                if (nrow(vSsub)>0)
                {
                  vSspl <- data.frame(stats::spline(vSsub$x, vSsub$y, n=nrow(vSsub)*10), p=panels[i], l=lines[j])
                  vSspl$ll <- stats::spline(vSsub$x, vSsub$ll, n=nrow(vSsub)*10)$y
                  vSspl$ul <- stats::spline(vSsub$x, vSsub$ul, n=nrow(vSsub)*10)$y
                  vS <- rbind(vS,vSspl)
                }
              }
            }

            vT <- vT[with(vT, order(x, p, l)), ]
            vS <- vS[with(vS, order(x, p, l)), ]
          }

          if (is.element("points", input$selGeon0))
            Geom_Point <- geom_point(size=3)
          else
            Geom_Point <- geom_point(size=0)

          graphics::plot(ggplot(data=vT, aes(x, y, group=l, color=l)) +
                         geom_line(data=vS, linewidth=1) +
                         Geom_Point +
                         geom_ribbon(data=vS, aes(x=x, ymin=ll, ymax=ul, fill = l), alpha=0.2, colour=NA) +
                         ggtitle(input$title0) +
                         scale_x_continuous(breaks = unique(vT$x)) +
                         xlab("relative duration") + ylab(paste0(input$replyVar0," (",scaleLab0(),")")) +
                         scale_colour_discrete(name=paste0(paste(input$replyLine0, collapse = " "),"\n")) +
                         facet_wrap(vars(p)) +
                         theme_bw() +
                         theme(text           =element_text(size=as.numeric(input$replyPoint0b), family=input$replyFont0b),
                               plot.title     =element_text(face="bold", hjust = 0.5),
                               legend.key.size=unit(1.5, 'lines'),
                               aspect.ratio   =0.67)+
                         guides(fill="none"))
        }
        else {}
      }

      res0 <- function()
      {
        if (length(input$replySize0b)==0)
          return( 72)
        
        if (input$replySize0b=="tiny"  )
          return( 54)
        if (input$replySize0b=="small" )
          return( 72)
        if (input$replySize0b=="normal")
          return( 90)
        if (input$replySize0b=="large" )
          return(108)
        if (input$replySize0b=="huge"  )
          return(144)
      }

      observeEvent(input$replySize0b,
      {
        output$graph0 <- renderPlot(height = 550, width = 700, res = res0(),
        {
          if (length(input$catXaxis0)>0)
          {
            plotGraph0()
          }
        })
      })

      output$Graph0 <- renderUI(
      {
        plotOutput("graph0", height="627px")
      })

      output$selFormat0a <- renderUI(
      {
        options <- c("txt","xlsx")
        selectInput('replyFormat0a', label=NULL, options, selected = options[2], selectize=FALSE, multiple=FALSE)
      })

      fileName0a <- function()
      {
        return(paste0("contoursTable.",input$replyFormat0a))
      }

      output$download0a <- downloadHandler(filename = fileName0a, content = function(file)
      {
        if (length(input$catXaxis0)>0)
        {
          vT <- vowelSub0()

          colnames(vT)[which(colnames(vT)=="sd")] <- "standard deviation"
          colnames(vT)[which(colnames(vT)=="se")] <- "standard error"
          colnames(vT)[which(colnames(vT)=="n" )] <- "number of observations"
          colnames(vT)[which(colnames(vT)=="ll")] <- "lower limit"
          colnames(vT)[which(colnames(vT)=="ul")] <- "upper limit"
        }
        else
          vT <- data.frame()

        if (input$replyFormat0a=="txt")
        {
          utils::write.table(vT, file, sep = "\t", na = "NA", dec = ".", row.names = FALSE, col.names = TRUE)
        }
        else

        if (input$replyFormat0a=="xlsx")
        {
          WriteXLS(vT, file, SheetNames = "table", row.names=FALSE, col.names=TRUE, BoldHeaderRow = TRUE, na = "NA", FreezeRow = 1, AdjWidth = TRUE)
        }
        else {}
      })

      output$selSize0b <- renderUI(
      {
        options <- c("tiny", "small", "normal", "large", "huge")
        selectInput('replySize0b', label=NULL, options, selected = options[3], selectize=FALSE, multiple=FALSE)
      })

      output$selFont0b <- renderUI(
      {
        options <- c("Courier" = "Courier", "Helvetica" = "Helvetica", "Times" = "Times")
        selectInput('replyFont0b', label=NULL, options, selected = "Helvetica", selectize=FALSE, multiple=FALSE)
      })

      output$selPoint0b <- renderUI(
      {
        options <- c(5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,36,40,44,48)
        selectInput('replyPoint0b', label=NULL, options, selected = 18, selectize=FALSE, multiple=FALSE)
      })

      output$selFormat0b <- renderUI(
      {
        options <- c("JPG","PNG","SVG","EPS","PDF","TEX")
        selectInput('replyFormat0b', label=NULL, options, selected = "PNG", selectize=FALSE, multiple=FALSE)
      })

      fileName0b <- function()
      {
        return(paste0("contoursPlot.",input$replyFormat0b))
      }

      output$download0b <- downloadHandler(filename = fileName0b, content = function(file)
      {
        grDevices::pdf(NULL)

        scale  <- 72/res0()
        width  <- convertUnit(x=unit(700, "pt"), unitTo="in", valueOnly=TRUE)
        height <- convertUnit(x=unit(550, "pt"), unitTo="in", valueOnly=TRUE)
        
        if ((length(input$catXaxis0)>0) && (nrow(vowelSub0())>0))
          plot <- plotGraph0()
        else
          plot <- ggplot()+theme_bw()
        
        show_modal_spinner()
        
        if (input$replyFormat0b=="JPG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="jpeg")
        else
        if (input$replyFormat0b=="PNG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="png" )
        else
        if (input$replyFormat0b=="SVG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="svg" )
        else
        if (input$replyFormat0b=="EPS")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_ps )
        else
        if (input$replyFormat0b=="PDF")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_pdf)
        else
        if (input$replyFormat0b=="TEX")
        {
          tikzDevice::tikz(file=file, width=width, height=height, engine='xetex')
          print(plot)
        }
        else {}

        grDevices::graphics.off()
        
        remove_modal_spinner()
      })

      ##########################################################################

      replyTimes10  <- reactive(input$replyTimes1)
      replyTimes1   <- debounce(replyTimes10 , 2000)

      replyTimesN10 <- reactive(input$replyTimesN1)
      replyTimesN1  <- debounce(replyTimesN10, 2000)

      vowelScale1 <- reactive(
      {
        return(vowelScale(vowelTab(),input$replyScale1,0))
      })

      vowelNorm1 <- reactive(
      {
        if (length(input$replyNormal1)==0)
          return(NULL)

        if (!is.null(replyTimesN1()))
          replyTimesN <- replyTimesN1()
        else
          return(NULL)

        indexDuration <- grep("^duration$", tolower(colnames(vowelScale1())))
        nPoints       <- (ncol(vowelScale1()) - indexDuration) / 5
        
        if (max(replyTimesN) > nPoints)
          replyTimesN <- Round(nPoints/2)
        else {}
        
        vL1 <- vowelLong1(vowelScale1(),replyTimesN)
        vL2 <- vowelLong2(vL1)
        vL3 <- vowelLong3(vL1)
        vL4 <- vowelLong4(vL1)
        vLD <- vowelLongD(vL1)
    
        return(vowelNormF(vowelScale1(), vL1, vL2, vL3, vL4, vLD, input$replyNormal1))
      })

      vowelSub1 <- reactive(
      {
        if ((is.null(vowelNorm1())) || (nrow(vowelNorm1())==0))
          return(NULL)

        vT <- vowelNorm1()

        vT$indexColor <- fuseCols(vowelNorm1(),input$replyColor1)
        vT$indexShape <- fuseCols(vowelNorm1(),input$replyShape1)
        vT$indexPlot  <- fuseCols(vowelNorm1(),input$replyPlot1)

        indexVowel <- grep("^vowel$", colnames(vowelNorm1()))

        ### check begin

        nPoints <- (ncol(vowelTab()) - (indexVowel + 1))/5

        if (max(as.numeric(replyTimes1()))>nPoints)
          return(NULL)
        else

        if (length(vT$indexColor)==0)
          return(NULL)
        else

        if (length(replyTimes1())>1)
        {}
        else

        if (input$axisZ!="--")
        {}
        else

        if (length(vT$indexShape)==0)
          return(NULL)
        else

        if (length(vT$indexPlot)==0)
          return(NULL)
        else {}

        ### check end

        if (length(input$catColor1)>0)
        {
          vT1 <- data.frame()

          for (q in (1:length(input$catColor1)))
          {
            vT1 <- rbind(vT1, subset(vT, indexColor==input$catColor1[q]))
          }
        }
        else
        {
          vT1 <- vT
        }

        if (length(input$catShape1)>0)
        {
          vT2 <- data.frame()

          for (q in (1:length(input$catShape1)))
          {
            vT2 <- rbind(vT2, subset(vT1, indexShape==input$catShape1[q]))
          }
        }
        else
        {
          vT2 <- vT1
        }

        if (length(input$catPlot1)>0)
        {
          vT3 <- data.frame()

          for (q in (1:length(input$catPlot1)))
          {
            vT3 <- rbind(vT3, subset(vT2, indexPlot==input$catPlot1[q]))
          }
        }
        else
        {
          vT3 <- vT2
        }

        vT <- vT3

        ###

        if (nrow(vT)>0)
        {
          vT0 <- data.frame()

          for (i in (1:length(replyTimes1())))
          {
            Code <- strtoi(replyTimes1()[i])

            indexF1 <- indexVowel + 4 + ((Code-1) * 5)
            indexF2 <- indexVowel + 5 + ((Code-1) * 5)
            indexF3 <- indexVowel + 6 + ((Code-1) * 5)

            if (length(input$catColor1)>0)
              Color <- vT$indexColor
            else
              Color <- rep("none",nrow(vT))

            if (length(input$catShape1)>0)
              Shape <- vT$indexShape
            else
              Shape <- rep("none",nrow(vT))

            if (length(input$catPlot1)>0)
              Plot  <- vT$indexPlot
            else
              Plot  <- rep("none",nrow(vT))

            if (input$axisX=="F1")
              Xaxis <- vT[,indexF1]
            if (input$axisX=="F2")
              Xaxis <- vT[,indexF2]
            if (input$axisX=="F3")
              Xaxis <- vT[,indexF3]

            if (input$axisY=="F1")
              Yaxis <- vT[,indexF1]
            if (input$axisY=="F2")
              Yaxis <- vT[,indexF2]
            if (input$axisY=="F3")
              Yaxis <- vT[,indexF3]

            if (input$axisZ=="--")
              Zaxis <- 0
            if (input$axisZ=="F1")
              Zaxis <- vT[,indexF1]
            if (input$axisZ=="F2")
              Zaxis <- vT[,indexF2]
            if (input$axisZ=="F3")
              Zaxis <- vT[,indexF3]

            if (input$axisX=="--")
              Xaxis <- 0
            if (input$axisY=="--")
              Yaxis <- 0
            if (input$axisZ=="--")
              Zaxis <- 0

            if (any(is.na(Xaxis)))
              Xaxis <- 0
            if (any(is.na(Yaxis)))
              Yaxis <- 0
            if (any(is.na(Zaxis)))
              Zaxis <- 0

            vT0 <- rbind(vT0, data.frame(speaker = vT$speaker  ,
                                         vowel   = vT$vowel    ,
                                         color   = Color       ,
                                         shape   = Shape       ,
                                         plot    = Plot        ,
                                         index   = rownames(vT),
                                         time    = i           ,
                                         X       = Xaxis,
                                         Y       = Yaxis,
                                         Z       = Zaxis))
          }

          if (input$average1 | input$ltf1)
            vT0 <- stats::aggregate(cbind(X,Y,Z)~speaker+vowel+color+shape+plot+time, data=vT0, FUN=mean)

          if (input$ltf1)
          {
            colnames(vT0)[2] <- "v0wel"
            colnames(vT0)[1] <- "vowel"

            vT0$v0wel   <- NULL
          }
          else
            vT0$speaker <- NULL

          vT <- vT0
        }
        else {}

        ###

        if ((nrow(vT)>0) & (input$average1 | input$ltf1))
        {
          vT <- stats::aggregate(cbind(X,Y,Z)~vowel+color+shape+plot+time, data=vT, FUN=mean)

          no <- nrow(stats::aggregate(cbind(X,Y,Z)~vowel+color+shape+plot, data=vT, FUN=mean))
          index <- seq(1:no)
          vT$index <- rep(index,length(replyTimes1()))
        }

        ###

        if (nrow(vT)>0)
        {
          vT$vowel <- factor(vT$vowel)
          vT$color <- factor(vT$color)
          vT$shape <- factor(vT$shape)
          vT$plot  <- factor(vT$plot)
          vT$time  <- factor(vT$time)
          vT$index <- factor(vT$index)

        # utils::write.table(vT, "vT.csv", sep = "\t", row.names = FALSE)
          return(vT)
        }
        else
        {
          return(data.frame())
        }
      })

      output$selTimes1 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        timeCode     <- getTimeCode()
        indexVowel   <- grep("^vowel$", colnames(vowelTab()))
        nColumns     <- ncol(vowelTab())
        nPoints      <- (nColumns - (indexVowel + 1))/5

        checkboxGroupInput('replyTimes1', 'Time points to be shown:', timeCode, selected = Round(nPoints/2), TRUE)
      })

      output$selScale1 <- renderUI(
      {
        selectInput('replyScale1', 'Scale:', optionsScale()[1:(length(optionsScale())-1)], selected = optionsScale()[1], selectize=FALSE, multiple=FALSE)
      })

      output$selNormal1 <- renderUI(
      {
        if (is.null(vowelTab()) || length(input$replyScale1)==0)
          return(NULL)

        onlyF1F2 <- ((input$axisX!="F3") & (input$axisY!="F3") & (input$axisZ!="F3"))
        selectInput('replyNormal1', 'Normalization:', optionsNormal(vowelTab(), input$replyScale1, TRUE, onlyF1F2), selected = optionsNormal(vowelTab(), input$replyScale1, TRUE, onlyF1F2)[1], selectize=FALSE, multiple=FALSE)
      })

      output$selTimesN <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if ((length(input$replyNormal1)>0) && ((input$replyNormal1=="") |
                                               (input$replyNormal1==" Peterson") |
                                               (input$replyNormal1==" Sussman") |
                                               (input$replyNormal1==" Syrdal & Gopal") |
                                               (input$replyNormal1==" Thomas & Kendall")))
          return(NULL)

        timeCode     <- getTimeCode()
        indexVowel   <- grep("^vowel$", colnames(vowelTab()))
        nColumns     <- ncol(vowelTab())
        nPoints      <- (nColumns - (indexVowel + 1))/5

        checkboxGroupInput('replyTimesN1', 'Normalization based on:', timeCode, selected = Round(nPoints/2), TRUE)
      })

      output$manScale <- renderUI(
      {
        if (input$axisZ=="--")
        {
          checkboxInput("selManual", "min/max", FALSE)
        }
      })

      output$selF1min <- renderUI(
      {
        if ((length(input$selManual)>0) && (input$selManual==TRUE) && (input$axisZ=="--"))
        {
          numericInput('replyXmin', 'min. x', value=NULL, step=10, width = "100%")
        }
      })

      output$selF1max <- renderUI(
      {
        if ((length(input$selManual)>0) && (input$selManual==TRUE) && (input$axisZ=="--"))
        {
          numericInput('replyXmax', 'max. x', value=NULL, step=10, width = "100%")
        }
      })

      output$selF2min <- renderUI(
      {
        if ((length(input$selManual)>0) && (input$selManual==TRUE) && (input$axisZ=="--"))
        {
          numericInput('replyYmin', 'min. y', value=NULL, step=10, width = "100%")
        }
      })

      output$selF2max <- renderUI(
      {
        if ((length(input$selManual)>0) && (input$selManual==TRUE) && (input$axisZ=="--"))
        {
          numericInput('replyYmax', 'max. y', value=NULL, step=10, width = "100%")
        }
      })

      output$selColor1 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[1:(indexVowel-1)]))

        if (!input$ltf1)
          options <- c(colnames(vowelTab()[indexVowel]),options)

        selectInput('replyColor1', 'Color variable:', options, selected=options[1], multiple=TRUE, selectize=FALSE, size=3, width="100%")
      })

      output$catColor1 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyColor1)>0)
          options <- unique(fuseCols(vowelTab(),input$replyColor1))
        else
          options <- NULL

        selectInput('catColor1', 'Select colors:', options, multiple=TRUE, selectize = FALSE, size=3, width="100%")
      })

      output$selShape1 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if ((length(replyTimes1())==1) && (input$axisZ=="--"))
        {
          indexVowel <- grep("^vowel$", colnames(vowelTab()))

          if (input$geon2 | input$geon3 | input$geon4 | input$geon5)
            options <- c()
          else
            options <- c(colnames(vowelTab()[1:(indexVowel-1)]))

          if (!input$ltf1)
            options <- c(colnames(vowelTab()[indexVowel]),options)
        }
        else
        {
          options <- "none"
        }

        selectInput('replyShape1', 'Shape variable:', options, selected = options[1], multiple=TRUE, selectize=FALSE, size=3, width="100%")
      })

      output$catShape1 <- renderUI(
      {
        if  (is.null(vowelTab()))
          return(NULL)

        if ((length(input$replyShape1)>0) && (length(replyTimes1())==1) && (input$axisZ=="--"))
        {
          if (input$geon2 | input$geon3 | input$geon4 | input$geon5)
            options <- NULL
          else
            options <- unique(fuseCols(vowelTab(),input$replyShape1))
        }
        else
          options <- NULL

        selectInput('catShape1', 'Select shapes:', options, multiple=TRUE, selectize = FALSE, size=3, width="100%")
      })

      output$selPlot1 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (input$axisZ=="--")
        {
          indexVowel <- grep("^vowel$", colnames(vowelTab()))
          options <- c(colnames(vowelTab()[1:(indexVowel-1)]))

          if (!input$ltf1)
            options <- c(colnames(vowelTab()[indexVowel]),options)
        }
        else
        {
          options <- "none"
        }

        selectInput('replyPlot1', 'Panel variable:', options, selected = options[1], multiple=TRUE, selectize=FALSE, size=3, width="100%")
      })

      output$catPlot1 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if ((length(input$replyPlot1)>0) && (input$axisZ=="--"))
          options <- unique(fuseCols(vowelTab(),input$replyPlot1))
        else
          options <- NULL

        selectInput('catPlot1', 'Select panels:', options, multiple=TRUE, selectize = FALSE, size=3, width="100%")
      })

      output$selGeon1 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if ((input$axisZ=="--") && (length(replyTimes1())<=1))
          tagList(splitLayout
          (
            cellWidths = c("19%", "17%", "15%", "21%", "19%"),

            checkboxInput("geon1", "labels" , value = FALSE),
            checkboxInput("geon2", "cent."  , value = FALSE),
            checkboxInput("geon3", "hull"   , value = FALSE),
            checkboxInput("geon4", "spokes" , value = FALSE),
            checkboxInput("geon5", "ellipse", value = FALSE)
          ))
        else

        if ((input$axisZ!="--") && (length(replyTimes1())<=1))
          tagList(splitLayout
          (
            cellWidths = c("19%", "19%", "19%"),

            checkboxInput("geon1", "labels", value = FALSE),
            checkboxInput("geon2", "lines" , value = TRUE )
          ))
        else

        if (length(replyTimes1())> 2)
          checkboxInput("geon1", "smooth trajectories", value = FALSE)
        else {}
      })

      output$selPars <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if ((input$axisZ=="--") && (length(replyTimes1())==1) && (length(input$geon5)>0) && input$geon5)
        {
          tagList(splitLayout
          (
            cellWidths = c("50%", "50%"),
            numericInput('replyLevel', 'Confidence level:', value=0.95, step=0.01, width = "100%"),
             selectInput('replyNoise', 'Noise level:', c("\u00B1 0.001 sd", "\u00B1 0.01 sd", "\u00B1 0.1 sd", "\u00B1 0 sd"), selected = "\u00B1 0.001 sd", multiple=FALSE, selectize=FALSE, width="100%")
          ))
        }
        else

        if  (input$axisZ!="--")
        {
          tagList(splitLayout
          (
            cellWidths = c("50%", "50%"),
            numericInput('replyPhi'  , 'Angle x-axis:', value=40, step=1, width = "100%"),
            numericInput('replyTheta', 'Angle z-axis:', value=30, step=1, width = "100%")
          ))
        }
        else
          return(NULL)
      })

      numColor <- function()
      {
        if ((length(input$replyColor1)>0) && (length(input$catColor1)>0))
          return(length(input$catColor1))
        else
          return(0)
      }

      numShape <- function()
      {
        if ((length(input$replyShape1)>0) && (length(input$catShape1)>0))
          return(length(input$catShape1))
        else
          return(0)
      }

      numAll <- function()
      {
        return(numColor()+numShape())
      }

      colPalette <- function(n,grayscale)
      {
        if (!grayscale)
        {
          labColors  <- c("#c87e66","#b58437","#988a00","#709000","#27942e","#00965c","#009482","#008ea3","#0081bd","#386acc","#8d46c8","#b315b1","#bd0088","#b61a51")
          labPalette <- grDevices::colorRampPalette(labColors, space = "Lab")

          if (n==1)
            return(labColors[c(10)])

          if (n==2)
            return(labColors[c(8,14)])

          if (n==3)
            return(labColors[c(5,10,13)])

          if (n==4)
            return(labColors[c(3,7,11,14)])

          if (n==5)
            return(labColors[c(3,5,9,11,14)])

          if (n==6)
            return(labColors[c(2,5,7,10,12,14)])

          if (n==7)
            return(labColors[c(2,4,5,8,10,12,14)])

          if (n==8)
            return(labColors[c(1,3,5,7,9,11,12,14)])

          if (n==9)
            return(labColors[c(1,3,4,6,8,10,11,12,14)])

          if (n==10)
            return(labColors[c(1,3,4,5,7,9,10,11,12,14)])

          if (n==11)
            return(labColors[c(1,2,4,5,6,7,9,10,11,12,14)])

          if (n==12)
            return(labColors[c(1,2,3,4,5,7,8,9,11,12,13,14)])

          if (n==13)
            return(labColors[c(1,2,3,4,5,6,7,8,10,11,12,13,14)])

          if (n==14)
            return(labColors[c(1,2,3,4,5,6,7,8,9,10,11,12,13,14)])

          if (n>=15)
            return(labPalette(n))
        }
        else
        {
          return(grDevices::gray(0:(n-1)/n))
        }
      }

      colPalette1 <- function(n)
      {
        return(colPalette(n,input$grayscale1))
      }

      shpPalette <- function()
      {
        return(c(19,1,17,2,15,0,18,5,3,4,8))
      }

      scaleLab1 <- function()
      {
        return(scaleLab(input$replyScale1))
      }

      plotGraph1 <- function()
      {
        if (is.null(vowelSub1()) || (nrow(vowelSub1())==0) | (length(replyTimes1())==0))
          return(NULL)

        if (input$replyNormal1=="")
          scaleNormalLab <- scaleLab1()
        else
          scaleNormalLab <- trimws(input$replyNormal1, "left")

        if ((length(replyTimes1())==1) && (!(input$geon2 | input$geon3 | input$geon4 | input$geon5)) && (input$axisZ=="--"))
        {
          vT <- vowelSub1()

          if ((numColor()>0) & (numShape()>0) & (numShape()<=11))
          {
            Basis <- ggplot(data=vT, aes(x=X, y=Y, color=color, shape=shape)) +
              scale_shape_manual(values=shpPalette())
            
            if (input$geon1)
              Basis <- Basis + geom_point(size=2.5) + geom_text_repel(position="identity", aes(label=vowel), hjust=0.5, vjust=0.5, family=input$replyFont1b, size=5, alpha=1.0, max.overlaps=100)
            else        
              Basis <- Basis + geom_point(size=2.5)
          }
          else
            
          if  (numColor()>0)
          {
            Basis <- ggplot(data=vT, aes(x=X, y=Y, color=color))
            
            if (input$geon1)
              Basis <- Basis + geom_text(position="identity", aes(label=vowel), hjust=0.5, vjust=0.5, family=input$replyFont1b, size=5, alpha=1.0)
            else        
              Basis <- Basis + geom_point(size=2.5)
          }
          else
            
          if ((numShape()>0) & (numShape()<=11))
          {
            Basis <- ggplot(data=vT, aes(x=X, y=Y, shape=shape)) +
              scale_shape_manual(values=shpPalette())
  
          if (input$geon1)
              Basis <- Basis + geom_point(size=2.5, colour=colPalette1(1)) + geom_text_repel(position="identity", aes(label=vowel), hjust=0.5, vjust=0.5, family=input$replyFont1b, size=5, alpha=1.0, max.overlaps=100)
            else
              Basis <- Basis + geom_point(size=2.5, colour=colPalette1(1))
          }
          else
          {
            Basis <- ggplot(data=vT, aes(x=X, y=Y, color=color))
            
            if (input$geon1)
              Basis <- Basis + geom_text(position="identity", aes(label=vowel), hjust=0.5, vjust=0.5, family=input$replyFont1b, size=5, alpha=1.0)
            else        
              Basis <- Basis + geom_point(size=2.5)
          }
  
          if (input$geon1 & (!is.null(input$replyColor1) && (length(input$replyColor1)==1) && (input$replyColor1=="vowel")))
            Basis <- Basis + guides(colour="none") + labs(shape=paste(input$replyShape1, collapse = " "))
          else
            Basis <- Basis + labs(colour=paste(input$replyColor1, collapse = " "), shape=paste(input$replyShape1, collapse = " "))

          if ((length(input$selManual)>0) && (input$selManual==TRUE))
          {
            scaleX <- scale_x_reverse(name=paste0(input$axisX," (",scaleNormalLab,")"), position="top"  , limits = c(input$replyXmax, input$replyXmin))
            scaleY <- scale_y_reverse(name=paste0(input$axisY," (",scaleNormalLab,")"), position="right", limits = c(input$replyYmax, input$replyYmin))
          }
          else
          {
            scaleX <- scale_x_reverse(name=paste0(input$axisX," (",scaleNormalLab,")"), position="top"  )
            scaleY <- scale_y_reverse(name=paste0(input$axisY," (",scaleNormalLab,")"), position="right")
          }

          if (length(input$catPlot1)>0)
          {
            Title <- ggtitle(input$title1)
            Facet <- facet_wrap(~plot)
          }
          else
          {
            Title <- ggtitle(input$title1)
            Facet <- facet_null()
          }

          if ((numAll()>0) & (numAll()<=18))
            Legend <- theme(legend.position="right")
          else
          if ((numColor()>0) & (numColor()<=18))
            Legend <- guides(shape="none")
          else
          if ((numShape()>0) & (numShape()<=11))
            Legend <- guides(color="none")
          else
            Legend <- theme(legend.position="none")

          graphics::plot(Basis + scaleX + scaleY + Title + Facet +
                         scale_color_manual(values=colPalette1(length(unique(vT$color)))) +
                         theme_bw() +
                         theme(text           =element_text(size=as.numeric(input$replyPoint1b), family=input$replyFont1b),
                               plot.title     =element_text(face="bold", hjust = 0.5),
                               legend.key.size=unit(1.5,'lines'),
                               aspect.ratio   =1) +
                         Legend)
        }
        else

        if ((length(replyTimes1())==1) && (input$geon2 | input$geon3 | input$geon4 | input$geon5) && (input$axisZ=="--"))
        {
          vT <- vowelSub1()

          centers <- stats::aggregate(cbind(X,Y)~color+plot, data=vT, FUN=mean)

          vT <- vT[order(vT$plot, vT$index, vT$time),]
          vT$index <- paste0(vT$time,vT$index)

          if ((input$geon5) & length(input$replyLevel)>0)
          {
            if (input$replyNoise == "\u00B1 0.001 sd")
              replyNoise <- 0.001
            
            if (input$replyNoise == "\u00B1 0.01 sd")
              replyNoise <- 0.01
            
            if (input$replyNoise == "\u00B1 0.1 sd")
              replyNoise <- 0.1
            
            if (input$replyNoise == "\u00B1 0 sd")
              replyNoise <- 0
            
            sdX <- stats::sd(vT$X) * replyNoise
            sdY <- stats::sd(vT$Y) * replyNoise
            sdZ <- stats::sd(vT$Z) * replyNoise
            
            set.seed(0)
            
            noiseX <- stats::runif(nrow(vT), -1*sdX, sdX)
            noiseY <- stats::runif(nrow(vT), -1*sdY, sdY)
            noiseZ <- stats::runif(nrow(vT), -1*sdZ, sdZ)
            
            vT$X <- vT$X + noiseX
            vT$Y <- vT$Y + noiseY
            vT$Z <- vT$Z + noiseZ
          }

          Basis <- ggplot(data = vT, aes(x=X, y=Y, fill=color, color=color))
          Fill  <- geom_blank()

          if (input$geon1)
            Points <- geom_text(position="identity", aes(label=vowel), hjust=0.5, vjust=0.5, family=input$replyFont1b, size=5, alpha=0.3)
          else
            Points <- geom_blank()

          if (input$geon2)
          {
            if (input$geon4)
              Centers <- geom_text(data=centers, position="identity", aes(label=color), hjust=0.5, vjust=0.5, family=input$replyFont1b, size= 7, alpha=1.0, color="black")
            else
              Centers <- geom_text(data=centers, position="identity", aes(label=color), hjust=0.5, vjust=0.5, family=input$replyFont1b, size=10, alpha=1.0)

            Legend <- theme(legend.position="none")
          }
          else
          {
            Centers <- geom_blank()
            Legend <- theme(legend.position="right")
          }

          if (input$geon3)
          {
            chulls <- plyr::ddply(vT, plyr::.(color,plot), function(df) df[grDevices::chull(df$X, df$Y), ])

            if ((length(unique(vT$color))==1) | (as.character(input$replyColor1)[1]=="vowel"))
            {
              Hull <- geom_polygon(data=chulls, aes(x=X, y=Y, group=color, fill=color), alpha=0.1)
              Fill <- scale_fill_manual(values=colPalette1(length(unique(vT$color))))
            }
            else

            if (length(unique(vT$color))> 1)
            {
              Hull <- geom_polygon(data=chulls, aes(x=X, y=Y, group=color, fill=color), alpha=0  )
              Fill <- scale_fill_manual(values=rep("white", length(unique(vT$color))))
            }
            else {}
          }
          else
          {
            Hull <- geom_blank()
          }

          if (input$geon4)
          {
            vT0 <- vT
            for (i in (1:nrow(vT0)))
            {
              centersSub <- subset(centers, (centers$color==vT0$color[i]) & (centers$plot==vT0$plot[i]))

              vT0$X[i] <- centersSub$X
              vT0$Y[i] <- centersSub$Y
            }

            vT0 <- rbind(vT,vT0)
            vT0 <- vT0[order(vT0$plot, vT0$index, vT0$time),]
            vT0$index <- paste0(vT0$time,vT0$index)

            Spokes <- geom_path(data=vT0, aes(group = index), arrow = arrow(ends = "last", length = unit(0, "inches")), size=1.0, alpha=0.3)
          }
          else
          {
            Spokes <- geom_blank()
          }

          if ((input$geon5) & length(input$replyLevel)>0)
          {
            if ((input$geon1) | (input$geon3))
              Ellipse <- stat_ellipse(position="identity", type="norm", level=input$replyLevel)
            else
            {
              if ((length(unique(vT$color))==1) | (as.character(input$replyColor1)[1]=="vowel"))
              {
                Ellipse <- stat_ellipse(position="identity", type="norm", level=input$replyLevel, geom="polygon", alpha=0.3)
                Fill <- scale_fill_manual(values=colPalette1(length(unique(vT$color))))
              }
              else

              if (length(unique(vT$color))> 1)
              {
                Ellipse <- stat_ellipse(position="identity", type="norm", level=input$replyLevel, geom="polygon", alpha=0  )
                Fill <- scale_fill_manual(values=rep("white", length(unique(vT$color))))
              }
              else {}
            }
          }
          else
          {
            Ellipse <- geom_blank()
          }

          if ((length(input$selManual)>0) && (input$selManual==TRUE))
          {
            scaleX <- scale_x_reverse(name=paste0(input$axisX," (",scaleNormalLab,")"), position="top"  , limits = c(input$replyXmax, input$replyXmin))
            scaleY <- scale_y_reverse(name=paste0(input$axisY," (",scaleNormalLab,")"), position="right", limits = c(input$replyYmax, input$replyYmin))
          }
          else
          {
            scaleX <- scale_x_reverse(name=paste0(input$axisX," (",scaleNormalLab,")"), position="top"  )
            scaleY <- scale_y_reverse(name=paste0(input$axisY," (",scaleNormalLab,")"), position="right")
          }

          if (length(input$catPlot1)>0)
          {
            Title <- ggtitle(input$title1)
            Facet <- facet_wrap(~plot)
          }
          else
          {
            Title <- ggtitle(input$title1)
            Facet <- facet_null()
          }

          if ((numColor()==0) | (numColor()>18))
          {
            Legend <- theme(legend.position="none")
          }

          graphics::plot(Basis + Points + Hull +Spokes + Ellipse + Centers + scaleX + scaleY + Title + Facet +
                         scale_color_manual(values=colPalette1(length(unique(vT$color)))) + Fill +
                         labs(colour=paste(input$replyColor1, collapse = " "), fill=paste(input$replyColor1, collapse = " ")) +
                         theme_bw() +
                         theme(text           =element_text(size=as.numeric(input$replyPoint1b), family=input$replyFont1b),
                               plot.title     =element_text(face="bold", hjust = 0.5),
                               legend.key.size=unit(1.5,'lines'),
                               aspect.ratio   =1) +
                         Legend)
        }
        else

        if ((length(replyTimes1())>1) && (input$axisZ=="--"))
        {
          vT <- vowelSub1()[order(vowelSub1()$index, vowelSub1()$time),]

          if (input$geon1)
          {
            xx <- c()
            yy <- c()

            for (i in unique(vT$index))
            {
              vTsub <- subset(vT, index==i)

              xx <- c(xx, stats::spline(vTsub$time, vTsub$X, n=length(replyTimes1())*10)$y)
              yy <- c(yy, stats::spline(vTsub$time, vTsub$Y, n=length(replyTimes1())*10)$y)
            }

            vT <- splitstackshape::expandRows(vT, 10, count.is.col = F, drop = F)
            vT$X <- xx
            vT$Y <- yy
          }

          Basis <- ggplot(data=vT, aes(x=X, y=Y, colour=color, label=""))

          if ((length(input$selManual)>0) && (input$selManual==TRUE))
          {
            scaleX <- scale_x_reverse(name=paste0(input$axisX," (",scaleNormalLab,")"), position="top"  , limits = c(input$replyXmax, input$replyXmin))
            scaleY <- scale_y_reverse(name=paste0(input$axisY," (",scaleNormalLab,")"), position="right", limits = c(input$replyYmax, input$replyYmin))
          }
          else
          {
            scaleX <- scale_x_reverse(name=paste0(input$axisX," (",scaleNormalLab,")"), position="top"  )
            scaleY <- scale_y_reverse(name=paste0(input$axisY," (",scaleNormalLab,")"), position="right")
          }

          if (length(input$catPlot1)>0)
          {
            Title <- ggtitle(input$title1)
            Facet <- facet_wrap(~plot)
          }
          else
          {
            Title <- ggtitle(input$title1)
            Facet <- facet_null()
          }

          if ((numColor()>0) & (numColor()<=18))
            Legend <- theme(legend.position="right")
          else
            Legend <- theme(legend.position="none")

          graphics::plot(Basis + scaleX + scaleY + Title + Facet +
                         geom_path(aes(group = index), arrow = arrow(ends = "last", length = unit(0.1, "inches")), size=0.7) +
                         scale_color_manual(values=colPalette1(length(unique(vT$color)))) +
                         labs(colour=paste(input$replyColor1, collapse = " ")) +
                         theme_bw() +
                         theme(text           =element_text(size=as.numeric(input$replyPoint1b), family=input$replyFont1b),
                               plot.title     =element_text(face="bold", hjust = 0.5),
                               legend.key.size=unit(1.5,'lines'),
                               aspect.ratio   =1) +
                         Legend
          )
        }
        else

        if ((length(replyTimes1())==1) && (input$axisZ!="--"))
        {
          vT <- vowelSub1()

          if (nrow(vT)==1)
            return(NULL)

          if ((input$axisX=="F1") | (input$axisX=="F2"))
            vT$X <- -1 * vT$X

          if ((input$axisY=="F1") | (input$axisY=="F2"))
            vT$Y <- -1 * vT$Y

          if ((input$axisZ=="F1") | (input$axisZ=="F2"))
            vT$Z <- -1 * vT$Z

          if (length(unique(vT$Z))==1)
          {
            zmin <- mean(vT$Z)-1
            zmax <- mean(vT$Z)+1
          }
          else
          {
            zmin <- min(vT$Z)
            zmax <- max(vT$Z)
          }

          if (input$geon1)
            cex <- 0.0
          else
            cex <- 1.0

          if (input$geon1)
            Cex <- 1.0
          else
            Cex <- 0.00001

          graphics::par(family = input$replyFont1b)
          Point <- as.numeric(input$replyPoint1b)/22

          if (input$geon2)
            alpha <- 0.2
          else
            alpha <- 0.0

          if ((length(input$replyPhi)==0) || (is.na(input$replyPhi)))
            Phi <- 40
          else
            Phi <- input$replyPhi

          if ((length(input$replyPhi)==0) || (is.na(input$replyTheta)))
            Theta <- 30
          else
            Theta <- input$replyTheta

          mar   <- ((length(unique(vT$color))-1)/length(unique(vT$color)))/2
          first <- 1+mar
          last  <- length(unique(vT$color))-mar
          step  <- (last-first)/(length(unique(vT$color))-1)

          at <- c(first)

          if (length(unique(vT$color))>2)
          {
            for (i in 1:(length(unique(vT$color))-2))
            {
              at <- c(at,first + (i*step))
            }
          }

          at <- c(at,last)

          if ((numColor()>1) & (numColor()<=18))
          {
            colvar <- as.integer(as.factor(vT$color))
            colkey <- list(at       = at,
                           side     = 4,
                           addlines = TRUE,
                           length   = 0.04*length(unique(vT$color)),
                           width    = 0.5,
                           labels   = unique(vT$color))
          }
          else
          {
            colvar <- F
            colkey <- FALSE
          }

          graphics::par(mar=c(1,2,2.0,4))

          scatter3D(x        = vT$X,
                    y        = vT$Y,
                    z        = vT$Z,
                    zlim     = c(zmin, zmax),
                    phi      = Phi,
                    theta    = Theta,
                    bty      = "g",
                    type     = "h",
                    cex      = 0,
                    alpha    = alpha,
                    ticktype = "detailed",
                    colvar   = colvar,
                    col      = colPalette1(length(unique(vT$color))),
                    colkey   = FALSE,
                    cex.main = Point * 1.75,
                    cex.lab  = Point,
                    cex.axis = Point,
                    main     = input$title1,
                    xlab     = paste0(input$axisX," (",scaleNormalLab,")"),
                    ylab     = paste0(input$axisY," (",scaleNormalLab,")"),
                    zlab     = paste0(input$axisZ," (",scaleNormalLab,")"),
                    add      = FALSE)

          scatter3D(x        = vT$X,
                    y        = vT$Y,
                    z        = vT$Z,
                    zlim     = c(zmin, zmax),
                    type     = "p",
                    pch      = 19,
                    cex      = cex,
                    alpha    = 1,
                    colvar   = colvar,
                    col      = colPalette1(length(unique(vT$color))),
                    colkey   = colkey,
                    add      = TRUE)

          text3D   (x        = vT$X,
                    y        = vT$Y,
                    z        = vT$Z,
                    zlim     = c(zmin, zmax),
                    cex      = Cex,
                    alpha    = 1,
                    labels   = as.character(vT$vowel),
                    colvar   = colvar,
                    col      = colPalette1(length(unique(vT$color))),
                    colkey   = FALSE,
                    add      = TRUE)

          graphics::par(mar=c(5.1,4.1,4.1,2.1))
        }
        else

        if ((length(replyTimes1())>1) && (input$axisZ!="--"))
        {
          vT <- vowelSub1()[order(vowelSub1()$index, vowelSub1()$time),]

          if (input$geon1)
          {
            xx <- c()
            yy <- c()
            zz <- c()

            for (i in unique(vT$index))
            {
              vTsub <- subset(vT, index==i)

              xx <- c(xx, stats::spline(vTsub$time, vTsub$X, n=length(replyTimes1())*10)$y)
              yy <- c(yy, stats::spline(vTsub$time, vTsub$Y, n=length(replyTimes1())*10)$y)
              zz <- c(zz, stats::spline(vTsub$time, vTsub$Z, n=length(replyTimes1())*10)$y)
            }

            vT <- splitstackshape::expandRows(vT, 10, count.is.col = F, drop = F)
            vT$time <- rep(seq(1, length(replyTimes1())*10), length(unique(vT$index)))
            vT$X <- xx
            vT$Y <- yy
            vT$Z <- zz
          }

          if ((input$axisX=="F1") | (input$axisX=="F2"))
            vT$X <- -1 * vT$X

          if ((input$axisY=="F1") | (input$axisY=="F2"))
            vT$Y <- -1 * vT$Y

          if ((input$axisZ=="F1") | (input$axisZ=="F2"))
            vT$Z <- -1 * vT$Z

          if (length(unique(vT$Z))==1)
          {
            zmin <- mean(vT$Z)-1
            zmax <- mean(vT$Z)+1
          }
          else
          {
            zmin <- min(vT$Z)
            zmax <- max(vT$Z)
          }

          graphics::par(family = input$replyFont1b)
          Point <- as.numeric(input$replyPoint1b)/22

          if ((length(input$replyPhi)==0) || (is.na(input$replyPhi)))
            Phi <- 40
          else
            Phi <- input$replyPhi

          if ((length(input$replyPhi)==0) || (is.na(input$replyTheta)))
            Theta <- 30
          else
            Theta <- input$replyTheta

          mar   <- ((length(unique(vT$color))-1)/length(unique(vT$color)))/2
          first <- 1+mar
          last  <- length(unique(vT$color))-mar
          step  <- (last-first)/(length(unique(vT$color))-1)

          at <- c(first)

          if (length(unique(vT$color))>2)
          {
            for (i in 1:(length(unique(vT$color))-2))
            {
              at <- c(at,first + (i*step))
            }
          }

          at <- c(at,last)

          if ((numColor()>1) & (numColor()<=18))
          {
            VT     <- subset(vT, time==1)

            colvar <- as.integer(as.factor(vT$color))
            ColVar <- as.integer(as.factor(VT$color))
            ColKey <- list(at       = at,
                           side     = 4,
                           addlines = TRUE,
                           length   = 0.04*length(unique(VT$color)),
                           width    = 0.5,
                           labels   = unique(VT$color))
          }
          else
          {
            colvar <- F
            ColVar <- F
            ColKey <- FALSE
          }

          graphics::par(mar=c(1,2,2.0,4))

          scatter3D(x        = vT$X,
                    y        = vT$Y,
                    z        = vT$Z,
                    zlim     = c(zmin, zmax),
                    phi      = Phi,
                    theta    = Theta,
                    bty      = "g",
                    type     = "h",
                    pch      = 19,
                    cex      = 0,
                    ticktype = "detailed",
                    colvar   = colvar,
                    col      = colPalette1(length(unique(vT$color))),
                    colkey   = FALSE,
                    main     = input$title1,
                    cex.main = Point * 1.75,
                    cex.lab  = Point,
                    cex.axis = Point,
                    alpha    = 0.2,
                    xlab     = paste0(input$axisX," (",scaleNormalLab,")"),
                    ylab     = paste0(input$axisY," (",scaleNormalLab,")"),
                    zlab     = paste0(input$axisZ," (",scaleNormalLab,")"),
                    add      = FALSE)

          nTimes <- length(unique(vT$time))

          if (nTimes > 2)
          {
            for (i in (2:(nTimes-1)))
            {
              vT0 <- subset(vT, time==i-1)
              vT1 <- subset(vT, time==i)

              arrows3D(x0     = vT0$X,
                       y0     = vT0$Y,
                       z0     = vT0$Z,
                       x1     = vT1$X,
                       y1     = vT1$Y,
                       z1     = vT1$Z,
                       zlim   = c(zmin, zmax),
                       colvar = ColVar,
                       col    = colPalette1(length(unique(vT0$color))),
                       colkey = FALSE,
                       code   = 0,
                       length = 0.2,
                       type   = "triangle",
                       lwd    = 2,
                       alpha  = 1,
                       add    = TRUE)
            }
          }

          vT0 <- subset(vT, time==nTimes-1)
          vT1 <- subset(vT, time==nTimes  )

          arrows3D(x0     = vT0$X,
                   y0     = vT0$Y,
                   z0     = vT0$Z,
                   x1     = vT1$X,
                   y1     = vT1$Y,
                   z1     = vT1$Z,
                   zlim   = c(zmin, zmax),
                   colvar = ColVar,
                   col    = colPalette1(length(unique(vT0$color))),
                   colkey = ColKey,
                   code   = 2,
                   length = 0.3,
                   type   = "simple",
                   lwd    = 2,
                   alpha  = 1,
                   add    = TRUE)

          graphics::par(mar=c(5.1,4.1,4.1,2.1))
        }
        else {}
      }

      res1 <- function()
      {
        if (length(input$replySize1b)==0)
          return( 72)
        
        if (input$replySize1b=="tiny"  )
          return( 54)
        if (input$replySize1b=="small" )
          return( 72)
        if (input$replySize1b=="normal")
          return( 90)
        if (input$replySize1b=="large" )
          return(108)
        if (input$replySize1b=="huge"  )
          return(144)
      }

      observeEvent(input$replySize1b,
      {
        output$graph1 <- renderPlot(height = 550, width = 700, res = res1(),
        {
          if (length(replyTimes1())>0)
          {
            plotGraph1()
          }
        })
      })

      output$Graph1 <- renderUI(
      {
        plotOutput("graph1", height="627px")
      })

      output$selFormat1a <- renderUI(
      {
        options <- c("txt","xlsx")
        selectInput('replyFormat1a', label=NULL, options, selected = options[2], selectize=FALSE, multiple=FALSE)
      })

      fileName1a <- function()
      {
        return(paste0("formantsTable.",input$replyFormat1a))
      }

      output$download1a <- downloadHandler(filename = fileName1a, content = function(file)
      {
        if (length(replyTimes1())>0)
        {
          vT <- vowelSub1()

          colnames(vT)[which(colnames(vT)=="X")] <- input$axisX
          colnames(vT)[which(colnames(vT)=="Y")] <- input$axisY
          colnames(vT)[which(colnames(vT)=="Z")] <- input$axisZ
        }
        else
          vT <- data.frame()

        if (input$replyFormat1a=="txt")
        {
          utils::write.table(vT, file, sep = "\t", na = "NA", dec = ".", row.names = FALSE, col.names = TRUE)
        }
        else

        if (input$replyFormat1a=="xlsx")
        {
          WriteXLS(vT, file, SheetNames = "table", row.names=FALSE, col.names=TRUE, BoldHeaderRow = TRUE, na = "NA", FreezeRow = 1, AdjWidth = TRUE)
        }
        else {}
      })

      output$selSize1b <- renderUI(
      {
        options <- c("tiny", "small", "normal", "large", "huge")
        selectInput('replySize1b', label=NULL, options, selected = options[3], selectize=FALSE, multiple=FALSE)
      })

      output$selFont1b <- renderUI(
      {
        options <- c("Courier" = "Courier", "Helvetica" = "Helvetica", "Times" = "Times")
        selectInput('replyFont1b', label=NULL, options, selected = "Helvetica", selectize=FALSE, multiple=FALSE)
      })

      output$selPoint1b <- renderUI(
      {
        options <- c(5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,36,40,44,48)
        selectInput('replyPoint1b', label=NULL, options, selected = 18, selectize=FALSE, multiple=FALSE)
      })

      output$selFormat1b <- renderUI(
      {
        options <- c("JPG","PNG","SVG","EPS","PDF","TEX")
        selectInput('replyFormat1b', label=NULL, options, selected = "PNG", selectize=FALSE, multiple=FALSE)
      })

      fileName1b <- function()
      {
        return(paste0("formantPlot.",input$replyFormat1b))
      }

      save2D <- function(file)
      {
        grDevices::pdf(NULL)

        scale  <- 72/res1()
        width  <- convertUnit(x=unit(700, "pt"), unitTo="in", valueOnly=TRUE)
        height <- convertUnit(x=unit(550, "pt"), unitTo="in", valueOnly=TRUE)
        
        if ((length(replyTimes1())>0) && (nrow(vowelSub1())>0))
          plot <- plotGraph1()
        else
          plot <- ggplot()+theme_bw()
        
        show_modal_spinner()
        
        if (input$replyFormat1b=="JPG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="jpeg")
        else
        if (input$replyFormat1b=="PNG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="png" )
        else
        if (input$replyFormat1b=="SVG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="svg" )
        else
        if (input$replyFormat1b=="EPS")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_ps )
        else
        if (input$replyFormat1b=="PDF")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_pdf)
        else    
        if (input$replyFormat1b=="TEX")
        {
          tikzDevice::tikz(file=file, width=width, height=height, engine='xetex')
          print(plot)
        }
        else {}

        grDevices::graphics.off()
        
        remove_modal_spinner()
      }

      save3D <- function(file)
      {
        grDevices::pdf(NULL)

        scale0  <- 300/res1()
        width0  <- 700 * scale0
        height0 <- 550 * scale0
        
        scale   <-  72/res1()
        width   <- convertUnit(x=unit(700, "pt"), unitTo="in", valueOnly=TRUE) * scale
        height  <- convertUnit(x=unit(550, "pt"), unitTo="in", valueOnly=TRUE) * scale

        show_modal_spinner()

        if (input$replyFormat1b=="JPG")
          grDevices::jpeg      (file, width = width0, height = height0, pointsize = 12, res = 300)
        else
        if (input$replyFormat1b=="PNG")
          grDevices::png       (file, width = width0, height = height0, pointsize = 12, res = 300)
        else
        if (input$replyFormat1b=="SVG")
          grDevices::svg       (file, width = width , height = height , pointsize = 12)
        else
        if (input$replyFormat1b=="EPS")
          grDevices ::cairo_ps (file, width = width , height = height , pointsize = 12)
        else
        if (input$replyFormat1b=="PDF")
          grDevices ::cairo_pdf(file, width = width , height = height , pointsize = 12)
        else    
        if (input$replyFormat1b=="TEX")
          tikzDevice::tikz     (file, width = width , height = height , pointsize = 12, engine='xetex')
        else {}

        if ((length(replyTimes1())>0) && (nrow(vowelSub1())>0))
          print(plotGraph1())
        else
          graphics::plot.new()

        grDevices::graphics.off()
        
        remove_modal_spinner()
      }

      output$download1b <- downloadHandler(filename = fileName1b, content = function(file)
      {
        if (input$axisZ=="--")
          save2D(file)
        else
          save3D(file)
      })

      ##########################################################################

      vowelScale4 <- reactive(
      {
        return(vowelScale(vowelTab(),input$replyScale4,0))
      })

      vowelDyn4 <- reactive(
      {
        if (is.null(vowelScale4()) || (nrow(vowelScale4())==0) || (length(input$replyVar4)<1) || (length(input$replyTimes4)<2))
          return(NULL)

        vT <- vowelScale4()

        indexVowel <- grep("^vowel$", colnames(vowelTab()))

        nColumns   <- ncol(vowelTab())
        nPoints    <- (nColumns - (indexVowel + 1))/5

        vT["dynamics"] <- 0

        for (i in 1:(length(input$replyTimes4)-1))
        {
          i1 <- as.numeric(input$replyTimes4[i  ])
          i2 <- as.numeric(input$replyTimes4[i+1])

          indexTime1 <- indexVowel + 2 + ((i1-1)*5)
          indexTime2 <- indexVowel + 2 + ((i2-1)*5)

          Var <- c("f0","F1","F2","F3")
          sum <- rep(0,nrow(vT))

          for (j in 1:4)
          {
            if (is.element(Var[j],input$replyVar4))
            {
              indexVar1 <- indexTime1 + j
              indexVar2 <- indexTime2 + j

              sum <- sum + (vT[,indexVar1] - vT[,indexVar2])^2
            }
          }

          VL     <- sqrt(sum)
          VL_roc <- VL / (vT[,indexTime2] - vT[,indexTime1])

          if (input$replyMethod4=="TL")
            vT$dynamics <- vT$dynamics + VL

          if (input$replyMethod4=="TL_roc")
            vT$dynamics <- vT$dynamics + VL_roc
        }

        return(vT)
      })

      vowelSub4 <- reactive(
      {
        if (is.null(vowelDyn4()) || (nrow(vowelDyn4())==0) || (length(input$catXaxis4)==0))
          return(NULL)

        vT <- vowelDyn4()

        indexVowel <- grep("^vowel$", colnames(vT))

        if (any(is.na(vT[,indexVowel+1])))
          vT[,indexVowel+1] <- 0

        vT$indexXaxis <- fuseCols(vowelDyn4(),input$replyXaxis4)
        vT$indexLine  <- fuseCols(vowelDyn4(),input$replyLine4)
        vT$indexPlot  <- fuseCols(vowelDyn4(),input$replyPlot4)

        if (input$selError4=="0%")
          z <- 0
        if (input$selError4=="90%")
          z <- 1.645
        if (input$selError4=="95%")
          z <- 1.96
        if (input$selError4=="99%")
          z <- 4.575

        vT <- subset(vT, is.element(vT$indexXaxis,input$catXaxis4))

        if (nrow(vT)==0)
          return(NULL)

        if (((length(input$catLine4)==0) | (length(input$catLine4)>14)) && (length(input$catPlot4)==0))
        {
          if (is.element("average",input$selGeon4))
          {
            vT <- data.frame(indexXaxis=vT$indexXaxis, speaker=vT$speaker, vowel=vT$vowel, dynamics=vT$dynamics)
            vT <- stats::aggregate(dynamics ~ indexXaxis + speaker + vowel, data=vT, FUN=mean)
            vT <- stats::aggregate(dynamics ~ indexXaxis +           vowel, data=vT, FUN=mean)
          }

          ag    <- stats::aggregate(vT$dynamics ~ vT$indexXaxis, FUN=mean)
          ag$sd <- stats::aggregate(vT$dynamics ~ vT$indexXaxis, FUN=sd)[,2]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(vT$dynamics ~ vT$indexXaxis, FUN=length)[,2]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure4=="SD")
          {
            ag$ll <- ag[,2] - z * ag$sd
            ag$ul <- ag[,2] + z * ag$sd
          }
          if (input$selMeasure4=="SE")
          {
            ag$ll <- ag[,2] - z * ag$se
            ag$ul <- ag[,2] + z * ag$se
          }

          ag <- ag[order(ag[,2]),]
          ag[,1] <- factor(ag[,1], levels=ag[,1])

          colnames(ag)[1] <- paste(input$replyXaxis4, collapse = " ")
          colnames(ag)[2] <- input$replyMethod4

          return(ag)
        }
        else

        if (((length(input$catLine4)==0) | (length(input$catLine4)>14)) && (length(input$catPlot4)>0))
        {
          vT <- subset(vT, is.element(vT$indexPlot,input$catPlot4))

          if (nrow(vT)==0)
            return(data.frame())

          if (is.element("average",input$selGeon4))
          {
            vT <- data.frame(indexXaxis=vT$indexXaxis, indexPlot=vT$indexPlot, speaker=vT$speaker, vowel=vT$vowel, dynamics=vT$dynamics)
            vT <- stats::aggregate(dynamics ~ indexXaxis + indexPlot + speaker + vowel, data=vT, FUN=mean)
            vT <- stats::aggregate(dynamics ~ indexXaxis + indexPlot +           vowel, data=vT, FUN=mean)
          }

          ag    <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexPlot, FUN=mean)
          ag$sd <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexPlot, FUN=sd)[,3]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexPlot, FUN=length)[,3]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure4=="SD")
          {
            ag$ll <- ag[,3] - z * ag$sd
            ag$ul <- ag[,3] + z * ag$sd
          }
          if (input$selMeasure4=="SE")
          {
            ag$ll <- ag[,3] - z * ag$se
            ag$ul <- ag[,3] + z * ag$se
          }

          ag <- ag[order(ag[,3]),]
          xx <- unique(ag[,1])

          ag0 <- data.frame()

          for (q in (1:length(xx)))
          {
            ag0 <- rbind(ag0,ag[ag[,1]==xx[q],])
          }

          ag <- ag0
          ag[,1] <- factor(ag[,1], levels=xx)
          ag[,2] <- as.character(ag[,2])

          ag <- ag[order(ag[,2]),]

          colnames(ag)[1] <- paste(input$replyXaxis4, collapse = " ")
          colnames(ag)[2] <- paste(input$replyPlot4 , collapse = " ")
          colnames(ag)[3] <- input$replyMethod4

          colnames(ag) <- make.unique(names(ag))

          return(ag)
        }
        else

        if (((length(input$catLine4)>0) & (length(input$catLine4)<=14)) && (length(input$catPlot4)==0))
        {
          vT <- subset(vT, is.element(vT$indexLine,input$catLine4))

          if (nrow(vT)==0)
            return(data.frame())

          if (is.element("average",input$selGeon4))
          {
            vT <- data.frame(indexXaxis=vT$indexXaxis, indexLine=vT$indexLine, speaker=vT$speaker, vowel=vT$vowel, dynamics=vT$dynamics)
            vT <- stats::aggregate(dynamics ~ indexXaxis + indexLine + speaker + vowel, data=vT, FUN=mean)
            vT <- stats::aggregate(dynamics ~ indexXaxis + indexLine +           vowel, data=vT, FUN=mean)
          }

          ag    <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexLine, FUN=mean)
          ag$sd <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexLine, FUN=sd)[,3]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexLine, FUN=length)[,3]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure4=="SD")
          {
            ag$ll <- ag[,3] - z * ag$sd
            ag$ul <- ag[,3] + z * ag$sd
          }
          if (input$selMeasure4=="SE")
          {
            ag$ll <- ag[,3] - z * ag$se
            ag$ul <- ag[,3] + z * ag$se
          }

          ag <- ag[order(ag[,3]),]
          xx <- unique(ag[,1])

          ag0 <- data.frame()

          for (q in (1:length(xx)))
          {
            ag0 <- rbind(ag0,ag[ag[,1]==xx[q],])
          }

          ag <- ag0
          ag[,1] <- factor(ag[,1], levels=xx)
          ag[,2] <- as.character(ag[,2])

          colnames(ag)[1] <- paste(input$replyXaxis4, collapse = " ")
          colnames(ag)[2] <- paste(input$replyLine4 , collapse = " ")
          colnames(ag)[3] <- input$replyMethod4

          colnames(ag) <- make.unique(names(ag))

          return(ag)
        }
        else

        if (((length(input$catLine4)>0) & (length(input$catLine4)<=14))  && (length(input$catPlot4)>0))
        {
          vT <- subset(vT, is.element(vT$indexLine,input$catLine4) & is.element(vT$indexPlot,input$catPlot4))

          if (nrow(vT)==0)
            return(data.frame())

          if (is.element("average",input$selGeon4))
          {
            vT <- data.frame(indexXaxis=vT$indexXaxis, indexPlot=vT$indexPlot, indexLine=vT$indexLine, speaker=vT$speaker, vowel=vT$vowel, dynamics=vT$dynamics)
            vT <- stats::aggregate(dynamics ~ indexXaxis + indexPlot + indexLine + speaker + vowel, data=vT, FUN=mean)
            vT <- stats::aggregate(dynamics ~ indexXaxis + indexPlot + indexLine +           vowel, data=vT, FUN=mean)
          }

          ag    <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexLine + vT$indexPlot, FUN=mean)
          ag$sd <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexLine + vT$indexPlot, FUN=sd)[,4]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(vT$dynamics ~ vT$indexXaxis + vT$indexLine + vT$indexPlot, FUN=length)[,4]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure4=="SD")
          {
            ag$ll <- ag[,4] - z * ag$sd
            ag$ul <- ag[,4] + z * ag$sd
          }
          if (input$selMeasure4=="SE")
          {
            ag$ll <- ag[,4] - z * ag$se
            ag$ul <- ag[,4] + z * ag$se
          }

          ag <- ag[order(ag[,4]),]
          xx <- unique(ag[,1])

          ag0 <- data.frame()

          for (q in (1:length(xx)))
          {
            ag0 <- rbind(ag0,ag[ag[,1]==xx[q],])
          }

          ag <- ag0
          ag[,1] <- factor(ag[,1], levels=xx)
          ag[,2] <- as.character(ag[,2])
          ag[,3] <- as.character(ag[,3])

          ag <- ag[order(ag[,3]),]

          colnames(ag)[1] <- paste(input$replyXaxis4, collapse = " ")
          colnames(ag)[2] <- paste(input$replyLine4 , collapse = " ")
          colnames(ag)[3] <- paste(input$replyPlot4 , collapse = " ")
          colnames(ag)[4] <- input$replyMethod4

          colnames(ag) <- make.unique(names(ag))

          return(ag)
        }
        else
          return(data.frame())
      })

      output$selScale4 <- renderUI(
      {
        selectInput('replyScale4', 'Scale:', optionsScale()[1:(length(optionsScale())-1)], selected = optionsScale()[1], selectize=FALSE, multiple=FALSE)
      })

      output$selMethod4 <- renderUI(
      {
        options <- c("Fox & Jacewicz (2009) TL"     = "TL",
                     "Fox & Jacewicz (2009) TL_roc" = "TL_roc")

        selectInput('replyMethod4', 'Method:', options, selected = options[1], selectize=FALSE, multiple=FALSE)
      })

      output$selGraph4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        options <- c("Dot plot","Bar chart")
        selectInput('replyGraph4', 'Select graph type:', options, selected = options[1], selectize=FALSE, multiple=FALSE, width="100%")
      })

      output$selVar4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        options <- c("f0","F1","F2","F3")
        selectInput('replyVar4', 'Variable:', options, selected = character(0), multiple=TRUE, selectize=FALSE, width="100%", size=4)
      })

      output$selTimes4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        timeCode   <- getTimeCode()
        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        nColumns   <- ncol(vowelTab())
        nPoints    <- (nColumns - (indexVowel + 1))/5

        selectInput('replyTimes4', 'Points:', timeCode, multiple=TRUE, selectize=FALSE, selected = character(0), width="100%")
      })

      output$selXaxis4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[indexVowel]),colnames(vowelTab()[1:(indexVowel-1)]))

        selectInput('replyXaxis4', 'Var. x-axis:', options, selected = options[1], multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$catXaxis4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyXaxis4)>0)
          options <- unique(fuseCols(vowelTab(),input$replyXaxis4))
        else
          options <- NULL

        selectInput('catXaxis4', 'Sel. categ.:', options, multiple=TRUE, selectize = FALSE, width="100%")
      })

      output$selLine4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[indexVowel]),colnames(vowelTab()[1:(indexVowel-1)]))

        indexVowel <- grep("^vowel$",options)
        selectInput('replyLine4', 'Color var.:', options, selected = options[1], multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$catLine4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyLine4)>0)
          options <- unique(fuseCols(vowelTab(),input$replyLine4))
        else
          options <- NULL

        selectInput('catLine4', 'Sel. colors:', options, multiple=TRUE, selectize = FALSE, width="100%")
      })

      output$selPlot4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[indexVowel]),colnames(vowelTab()[1:(indexVowel-1)]))

        selectInput('replyPlot4', 'Panel var.:', options, selected = options[1], multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$catPlot4 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyPlot4)>0)
          options <- unique(fuseCols(vowelTab(),input$replyPlot4))
        else
          options <- NULL

        selectInput('catPlot4', 'Sel. panels:', options, multiple=TRUE, selectize = FALSE, width="100%")
      })

      scaleLab4 <- function()
      {
        return(scaleLab(input$replyScale4))
      }

      plotGraph4 <- function()
      {
        if (is.null(vowelSub4()) || (nrow(vowelSub4())==0))
          return(NULL)

        if (input$selError4=="0%")
          w <- 0
        if (input$selError4=="90%")
          w <- 0.4
        if (input$selError4=="95%")
          w <- 0.4
        if (input$selError4=="99%")
          w <- 0.4

        if (is.element("rotate x-axis labels",input$selGeon4))
          Angle = 90
        else
          Angle = 0

        if (((length(input$catLine4)==0) | (length(input$catLine4)>14)) && (length(input$catPlot4)==0))
        {
          if (input$replyGraph4=="Dot plot")
          {
            gp <- ggplot(data=vowelSub4(), aes(x=vowelSub4()[,1], y=vowelSub4()[,2], group=1)) +
                  geom_point(colour="indianred2", size=3) +
                  geom_errorbar(colour="indianred2", aes(ymin=ll, ymax=ul), width=w) +
                  ggtitle(input$title4) +
                  xlab(paste(input$replyXaxis4, collapse = " ")) + ylab(paste0(input$replyMethod4," ", paste(input$replyVar4,collapse = ' ')," (",scaleLab4(),")")) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint4b), family=input$replyFont4b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        aspect.ratio   =0.67)
          }
          else

          if (input$replyGraph4=="Bar chart")
          {
            gp <- ggplot(data=vowelSub4(), aes(x=vowelSub4()[,1], y=vowelSub4()[,2])) +
                  geom_bar(stat="identity", colour="black", fill="indianred2", size=.3) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w) +
                  ggtitle(input$title4) +
                  xlab(paste(input$replyXaxis4, collapse = " ")) + ylab(paste0(input$replyMethod4," ", paste(input$replyVar4,collapse = ' ')," (",scaleLab4(),")")) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint4b), family=input$replyFont4b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        aspect.ratio   =0.67)
          }
        }
        else

        if (((length(input$catLine4)==0) | (length(input$catLine4)>14)) && (length(input$catPlot4)>0))
        {
          if (input$replyGraph4=="Dot plot")
          {
            gp <- ggplot(data=vowelSub4(), aes(x=vowelSub4()[,1], y=vowelSub4()[,3], group=1)) +
                  geom_point(colour="indianred2", size=3) +
                  geom_errorbar(colour="indianred2", aes(ymin=ll, ymax=ul), width=w) +
                  ggtitle(input$title4) +
                  xlab(paste(input$replyXaxis4, collapse = " ")) + ylab(paste0(input$replyMethod4," ", paste(input$replyVar4,collapse = ' ')," (",scaleLab4(),")")) +
                  facet_wrap(~vowelSub4()[,2]) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint4b), family=input$replyFont4b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        aspect.ratio   =0.67)
          }

          else

          if (input$replyGraph4=="Bar chart")
          {
            gp <- ggplot(data=vowelSub4(), aes(x=vowelSub4()[,1], y=vowelSub4()[,3])) +
                  geom_bar(stat="identity", colour="black", fill="indianred2", size=.3) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w) +
                  ggtitle(input$title4) +
                  xlab(paste(input$replyXaxis4, collapse = " ")) + ylab(paste0(input$replyMethod4," ", paste(input$replyVar4,collapse = ' ')," (",scaleLab4(),")")) +
                  facet_wrap(~vowelSub4()[,2]) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint4b), family=input$replyFont4b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        aspect.ratio   =0.67)
          }
          else {}
        }
        else

        if (((length(input$catLine4)>0) & (length(input$catLine4)<=14)) && (length(input$catPlot4)==0))
        {
          if (input$replyGraph4=="Dot plot")
          {
            pd <- position_dodge(0.7)

            gp <- ggplot(data=vowelSub4(), aes(x=vowelSub4()[,1], y=vowelSub4()[,3], group=vowelSub4()[,2], color=vowelSub4()[,2])) +
                  geom_point(size=3, position=pd) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w, position=pd) +
                  ggtitle(input$title4) +
                  xlab(paste(input$replyXaxis4, collapse = " ")) + ylab(paste0(input$replyMethod4," ", paste(input$replyVar4,collapse = ' ')," (",scaleLab4(),")")) +
                  scale_colour_discrete(name=paste0(paste(input$replyLine4, collapse = " "),"\n")) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint4b), family=input$replyFont4b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        legend.key.size=unit(1.5, 'points'),
                        aspect.ratio   =0.67)
          }
          else

          if (input$replyGraph4=="Bar chart")
          {
            pd <- position_dodge(0.9)

            gp <- ggplot(data=vowelSub4(), aes(x=vowelSub4()[,1], y=vowelSub4()[,3], fill=vowelSub4()[,2])) +
                  geom_bar(position=position_dodge(), stat="identity", colour="black", size=.3) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w, position=pd) +
                  ggtitle(input$title4) +
                  xlab(paste(input$replyXaxis4, collapse = " ")) + ylab(paste0(input$replyMethod4," ", paste(input$replyVar4,collapse = ' ')," (",scaleLab4(),")")) +
                  scale_fill_hue(name=paste0(paste(input$replyLine4, collapse = " "),"\n")) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint4b), family=input$replyFont4b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        legend.key.size=unit(1.5, 'lines'),
                        aspect.ratio   =0.67)
          }
          else {}

          if (input$replyXaxis4==input$replyLine4)
            gp <- gp + theme(axis.title.x=element_blank(),
                             axis.text.x =element_blank(),
                             axis.ticks.x=element_blank())
          else {}
        }
        else

        if (((length(input$catLine4)>0) & (length(input$catLine4)<=14))  && (length(input$catPlot4)>0))
        {
          if (input$replyGraph4=="Dot plot")
          {
            pd <- position_dodge(0.5)

            gp <- ggplot(data=vowelSub4(), aes(x=vowelSub4()[,1], y=vowelSub4()[,4], group=vowelSub4()[,2], color=vowelSub4()[,2])) +
                  geom_point(size=3, position=pd) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w, position=pd) +
                  ggtitle(input$title4) +
                  xlab(paste(input$replyXaxis4, collapse = " ")) + ylab(paste0(input$replyMethod4," ", paste(input$replyVar4,collapse = ' ')," (",scaleLab4(),")")) +
                  scale_colour_discrete(name=paste0(paste(input$replyLine4, collapse = " "),"\n")) +
                  facet_wrap(~vowelSub4()[,3]) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint4b), family=input$replyFont4b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        legend.key.size=unit(1.5, 'points'),
                        aspect.ratio   =0.67)
          }
          else

          if (input$replyGraph4=="Bar chart")
          {
            pd <- position_dodge(0.9)

            gp <- ggplot(data=vowelSub4(), aes(x=vowelSub4()[,1], y=vowelSub4()[,4], fill=vowelSub4()[,2])) +
                  geom_bar(position=position_dodge(), stat="identity", colour="black", size=.3) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w, position=pd) +
                  ggtitle(input$title4) +
                  xlab(paste(input$replyXaxis4, collapse = " ")) + ylab(paste0(input$replyMethod4," ", paste(input$replyVar4,collapse = ' ')," (",scaleLab4(),")")) +
                  scale_fill_hue(name=paste0(paste(input$replyLine4, collapse = " "),"\n")) +
                  facet_wrap(~vowelSub4()[,3]) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint4b), family=input$replyFont4b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        legend.key.size=unit(1.5, 'lines'),
                        aspect.ratio   =0.67)
          }
          else {}

          if (input$replyXaxis4==input$replyLine4)
            gp <- gp + theme(axis.title.x=element_blank(),
                             axis.text.x =element_blank(),
                             axis.ticks.x=element_blank())
          else {}
        }
        else {}

        return(graphics::plot(gp))
      }

      res4 <- function()
      {
        if (length(input$replySize4b)==0)
          return( 72)
        
        if (input$replySize4b=="tiny"  )
          return( 54)
        if (input$replySize4b=="small" )
          return( 72)
        if (input$replySize4b=="normal")
          return( 90)
        if (input$replySize4b=="large" )
          return(108)
        if (input$replySize4b=="huge"  )
          return(144)
      }

      observeEvent(input$replySize4b,
      {
        output$graph4 <- renderPlot(height = 550, width = 700, res = res4(),
        {
          if ((length(input$replyVar4)>0) & (length(input$replyTimes4)>1) & (length(input$catXaxis4)>0))
          {
            plotGraph4()
          }
        })
      })

      output$Graph4 <- renderUI(
      {
        plotOutput("graph4", height="627px")
      })

      output$selFormat4a <- renderUI(
      {
        options <- c("txt","xlsx")
        selectInput('replyFormat4a', label=NULL, options, selected = options[2], selectize=FALSE, multiple=FALSE)
      })

      fileName4a <- function()
      {
        return(paste0("dynamicsTable.",input$replyFormat4a))
      }

      output$download4a <- downloadHandler(filename = fileName4a, content = function(file)
      {
        if ((length(input$replyVar4)>0) & (length(input$replyTimes4)>1) & (length(input$catXaxis4)>0))
        {
          vT <- vowelSub4()

          colnames(vT)[which(colnames(vT)=="sd")] <- "standard deviation"
          colnames(vT)[which(colnames(vT)=="se")] <- "standard error"
          colnames(vT)[which(colnames(vT)=="n" )] <- "number of observations"
          colnames(vT)[which(colnames(vT)=="ll")] <- "lower limit"
          colnames(vT)[which(colnames(vT)=="ul")] <- "upper limit"
        }
        else
          vT <- data.frame()

        if (input$replyFormat4a=="txt")
        {
          utils::write.table(vT, file, sep = "\t", na = "NA", dec = ".", row.names = FALSE, col.names = TRUE)
        }
        else

        if (input$replyFormat4a=="xlsx")
        {
          WriteXLS(vT, file, SheetNames = "table", row.names=FALSE, col.names=TRUE, BoldHeaderRow = TRUE, na = "NA", FreezeRow = 1, AdjWidth = TRUE)
        }
        else {}
      })

      output$selSize4b <- renderUI(
      {
        options <- c("tiny", "small", "normal", "large", "huge")
        selectInput('replySize4b', label=NULL, options, selected = options[3], selectize=FALSE, multiple=FALSE)
      })

      output$selFont4b <- renderUI(
      {
        options <- c("Courier" = "Courier", "Helvetica" = "Helvetica", "Times" = "Times")
        selectInput('replyFont4b', label=NULL, options, selected = "Helvetica", selectize=FALSE, multiple=FALSE)
      })

      output$selPoint4b <- renderUI(
      {
        options <- c(5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,36,40,44,48)
        selectInput('replyPoint4b', label=NULL, options, selected = 18, selectize=FALSE, multiple=FALSE)
      })

      output$selFormat4b <- renderUI(
      {
        options <- c("JPG","PNG","SVG","EPS","PDF","TEX")
        selectInput('replyFormat4b', label=NULL, options, selected = "PNG", selectize=FALSE, multiple=FALSE)
      })

      fileName4b <- function()
      {
        return(paste0("dynamicsPlot.",input$replyFormat4b))
      }

      output$download4b <- downloadHandler(filename = fileName4b, content = function(file)
      {
        grDevices::pdf(NULL)

        scale  <- 72/res4()
        width  <- convertUnit(x=unit(700, "pt"), unitTo="in", valueOnly=TRUE)
        height <- convertUnit(x=unit(550, "pt"), unitTo="in", valueOnly=TRUE)
        
        if ((length(input$replyVar4)>0) & (length(input$replyTimes4)>1) & (length(input$catXaxis4)>0) && (nrow(vowelSub4())>0))
          plot <- plotGraph4()
        else
          plot <- ggplot()+theme_bw()
          
        show_modal_spinner()
        
        if (input$replyFormat4b=="JPG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="jpeg")
        else
        if (input$replyFormat4b=="PNG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="png" )
        else
        if (input$replyFormat4b=="SVG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="svg" )
        else
        if (input$replyFormat4b=="EPS")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_ps )
        else
        if (input$replyFormat4b=="PDF")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_pdf)
        else
        if (input$replyFormat4b=="TEX")
        {
          tikzDevice::tikz(file=file, width=width, height=height, engine='xetex')
          print(plot)
        }
        else {}

        grDevices::graphics.off()
        
        remove_modal_spinner()
      })

      ##########################################################################

      vowelNorm2 <- reactive(
      {
        return(vowelNormD(vowelTab(),input$replyNormal2))
      })

      vowelSub2 <- reactive(
      {
        if (is.null(vowelNorm2()) || (nrow(vowelNorm2())==0) || (length(input$catXaxis2)==0))
          return(NULL)

        vT <- vowelNorm2()

        indexVowel <- grep("^vowel$", colnames(vT))

        if (any(is.na(vT[,indexVowel+1])))
          vT[,indexVowel+1] <- 0

        vT$indexXaxis <- fuseCols(vowelNorm2(),input$replyXaxis2)
        vT$indexLine  <- fuseCols(vowelNorm2(),input$replyLine2)
        vT$indexPlot  <- fuseCols(vowelNorm2(),input$replyPlot2)

        if (input$selError2=="0%")
          z <- 0
        if (input$selError2=="90%")
          z <- 1.645
        if (input$selError2=="95%")
          z <- 1.96
        if (input$selError2=="99%")
          z <- 2.575

        vT <- subset(vT, is.element(vT$indexXaxis,input$catXaxis2))

        if (nrow(vT)==0)
          return(NULL)

        if (((length(input$catLine2)==0) | (length(input$catLine2)>14)) && (length(input$catPlot2)==0))
        {
          if (is.element("average",input$selGeon2))
          {
            vT <- data.frame(indexXaxis=vT$indexXaxis, speaker=vT$speaker, vowel=vT$vowel, duration=vT$duration)
            vT <- stats::aggregate(duration ~ indexXaxis + speaker + vowel, data=vT, FUN=mean)
            vT <- stats::aggregate(duration ~ indexXaxis +           vowel, data=vT, FUN=mean)
          }

          ag    <- stats::aggregate(vT$duration ~ vT$indexXaxis, FUN=mean)
          ag$sd <- stats::aggregate(vT$duration ~ vT$indexXaxis, FUN=sd)[,2]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(vT$duration ~ vT$indexXaxis, FUN=length)[,2]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure2=="SD")
          {
            ag$ll <- ag[,2] - z * ag$sd
            ag$ul <- ag[,2] + z * ag$sd
          }
          if (input$selMeasure2=="SE")
          {
            ag$ll <- ag[,2] - z * ag$se
            ag$ul <- ag[,2] + z * ag$se
          }

          ag <- ag[order(ag[,2]),]
          ag[,1] <- factor(ag[,1], levels=ag[,1])

          colnames(ag)[1] <- paste(input$replyXaxis2, collapse = " ")
          colnames(ag)[2] <- "duration"

          return(ag)
        }
        else

        if (((length(input$catLine2)==0) | (length(input$catLine2)>14)) && (length(input$catPlot2)>0))
        {
          vT <- subset(vT, is.element(vT$indexPlot,input$catPlot2))

          if (nrow(vT)==0)
            return(data.frame())

          if (is.element("average",input$selGeon2))
          {
            vT <- data.frame(indexXaxis=vT$indexXaxis, indexPlot=vT$indexPlot, speaker=vT$speaker, vowel=vT$vowel, duration=vT$duration)
            vT <- stats::aggregate(duration ~ indexXaxis + indexPlot + speaker + vowel, data=vT, FUN=mean)
            vT <- stats::aggregate(duration ~ indexXaxis + indexPlot +           vowel, data=vT, FUN=mean)
          }

          ag    <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexPlot, FUN=mean)
          ag$sd <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexPlot, FUN=sd)[,3]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexPlot, FUN=length)[,3]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure2=="SD")
          {
            ag$ll <- ag[,3] - z * ag$sd
            ag$ul <- ag[,3] + z * ag$sd
          }
          if (input$selMeasure2=="SE")
          {
            ag$ll <- ag[,3] - z * ag$se
            ag$ul <- ag[,3] + z * ag$se
          }

          ag <- ag[order(ag[,3]),]
          xx <- unique(ag[,1])

          ag0 <- data.frame()

          for (q in (1:length(xx)))
          {
            ag0 <- rbind(ag0,ag[ag[,1]==xx[q],])
          }

          ag <- ag0
          ag[,1] <- factor(ag[,1], levels=xx)
          ag[,2] <- as.character(ag[,2])

          ag <- ag[order(ag[,2]),]

          colnames(ag)[1] <- paste(input$replyXaxis2, collapse = " ")
          colnames(ag)[2] <- paste(input$replyPlot2 , collapse = " ")
          colnames(ag)[3] <- "duration"

          colnames(ag) <- make.unique(names(ag))

          return(ag)
        }
        else

        if (((length(input$catLine2)>0) & (length(input$catLine2)<=14)) && (length(input$catPlot2)==0))
        {
          vT <- subset(vT, is.element(vT$indexLine,input$catLine2))

          if (nrow(vT)==0)
            return(data.frame())

          if (is.element("average",input$selGeon2))
          {
            vT <- data.frame(indexXaxis=vT$indexXaxis, indexLine=vT$indexLine, speaker=vT$speaker, vowel=vT$vowel, duration=vT$duration)
            vT <- stats::aggregate(duration ~ indexXaxis + indexLine + speaker + vowel, data=vT, FUN=mean)
            vT <- stats::aggregate(duration ~ indexXaxis + indexLine +           vowel, data=vT, FUN=mean)
          }

          ag    <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexLine, FUN=mean)
          ag$sd <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexLine, FUN=sd)[,3]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexLine, FUN=length)[,3]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure2=="SD")
          {
            ag$ll <- ag[,3] - z * ag$sd
            ag$ul <- ag[,3] + z * ag$sd
          }
          if (input$selMeasure2=="SE")
          {
            ag$ll <- ag[,3] - z * ag$se
            ag$ul <- ag[,3] + z * ag$se
          }

          ag <- ag[order(ag[,3]),]
          xx <- unique(ag[,1])

          ag0 <- data.frame()

          for (q in (1:length(xx)))
          {
            ag0 <- rbind(ag0,ag[ag[,1]==xx[q],])
          }

          ag <- ag0
          ag[,1] <- factor(ag[,1], levels=xx)
          ag[,2] <- as.character(ag[,2])

          colnames(ag)[1] <- paste(input$replyXaxis2, collapse = " ")
          colnames(ag)[2] <- paste(input$replyLine2 , collapse = " ")
          colnames(ag)[3] <- "duration"

          colnames(ag) <- make.unique(names(ag))

          return(ag)
        }
        else

        if (((length(input$catLine2)>0) & (length(input$catLine2)<=14))  && (length(input$catPlot2)>0))
        {
          vT <- subset(vT, is.element(vT$indexLine,input$catLine2) & is.element(vT$indexPlot,input$catPlot2))

          if (nrow(vT)==0)
            return(data.frame())

          if (is.element("average",input$selGeon2))
          {
            vT <- data.frame(indexXaxis=vT$indexXaxis, indexPlot=vT$indexPlot, indexLine=vT$indexLine, speaker=vT$speaker, vowel=vT$vowel, duration=vT$duration)
            vT <- stats::aggregate(duration ~ indexXaxis + indexPlot + indexLine + speaker + vowel, data=vT, FUN=mean)
            vT <- stats::aggregate(duration ~ indexXaxis + indexPlot + indexLine +           vowel, data=vT, FUN=mean)
          }

          ag    <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexLine + vT$indexPlot, FUN=mean)
          ag$sd <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexLine + vT$indexPlot, FUN=sd)[,4]
          ag$sd[is.na(ag$sd)] <- 0
          ag$n  <- stats::aggregate(vT$duration ~ vT$indexXaxis + vT$indexLine + vT$indexPlot, FUN=length)[,4]
          ag$se <- ag$sd / sqrt(ag$n)

          if (input$selMeasure2=="SD")
          {
            ag$ll <- ag[,4] - z * ag$sd
            ag$ul <- ag[,4] + z * ag$sd
          }
          if (input$selMeasure2=="SE")
          {
            ag$ll <- ag[,4] - z * ag$se
            ag$ul <- ag[,4] + z * ag$se
          }

          ag <- ag[order(ag[,4]),]
          xx <- unique(ag[,1])

          ag0 <- data.frame()

          for (q in (1:length(xx)))
          {
            ag0 <- rbind(ag0,ag[ag[,1]==xx[q],])
          }

          ag <- ag0
          ag[,1] <- factor(ag[,1], levels=xx)
          ag[,2] <- as.character(ag[,2])
          ag[,3] <- as.character(ag[,3])

          ag <- ag[order(ag[,3]),]

          colnames(ag)[1] <- paste(input$replyXaxis2, collapse = " ")
          colnames(ag)[2] <- paste(input$replyLine2 , collapse = " ")
          colnames(ag)[3] <- paste(input$replyPlot2 , collapse = " ")
          colnames(ag)[4] <- "duration"

          colnames(ag) <- make.unique(names(ag))

          return(ag)
        }
        else
          return(data.frame())
      })

      output$selNormal2 <- renderUI(
      {
        options <- c("None" = "",
                     "Lobanov (1971)" = " Lobanov")

        selectInput('replyNormal2', 'Normalization:', options, selected = options[1], selectize=FALSE, multiple=FALSE, width="100%")
      })

      output$selGraph2 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        options <- c("Dot plot","Bar chart")
        selectInput('replyGraph2', 'Select graph type:', options, selected = options[1], selectize=FALSE, multiple=FALSE, width="100%")
      })

      output$selXaxis2 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[indexVowel]),colnames(vowelTab()[1:(indexVowel-1)]))

        selectInput('replyXaxis2', 'Variable x-axis:', options, selected = options[1], multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$catXaxis2 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyXaxis2)>0)
          options <- unique(fuseCols(vowelTab(),input$replyXaxis2))
        else
          options <- NULL

        selectInput('catXaxis2', 'Sel. categories:', options, multiple=TRUE, selectize = FALSE, width="100%")
      })

      output$selLine2 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[indexVowel]),colnames(vowelTab()[1:(indexVowel-1)]))

        indexVowel <- grep("^vowel$",options)
        selectInput('replyLine2', 'Color variable:', options, selected = options[1], multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$catLine2 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyLine2)>0)
          options <- unique(fuseCols(vowelTab(),input$replyLine2))
        else
          options <- NULL

        selectInput('catLine2', 'Select colors:', options, multiple=TRUE, selectize = FALSE, width="100%")
      })

      output$selPlot2 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[indexVowel]),colnames(vowelTab()[1:(indexVowel-1)]))

        selectInput('replyPlot2', 'Panel variable:', options, selected = options[1], multiple=TRUE, selectize=FALSE, width="100%")
      })

      output$catPlot2 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyPlot2)>0)
          options <- unique(fuseCols(vowelTab(),input$replyPlot2))
        else
          options <- NULL

        selectInput('catPlot2', 'Select panels:', options, multiple=TRUE, selectize = FALSE, width="100%")
      })

      plotGraph2 <- function()
      {
        if (is.null(vowelSub2()) || (nrow(vowelSub2())==0))
          return(NULL)

        if (input$selError2=="0%")
          w <- 0
        if (input$selError2=="90%")
          w <- 0.4
        if (input$selError2=="95%")
          w <- 0.4
        if (input$selError2=="99%")
          w <- 0.4

        if (is.element("rotate x-axis labels",input$selGeon2))
          Angle = 90
        else
          Angle = 0

        if (((length(input$catLine2)==0) | (length(input$catLine2)>14)) && (length(input$catPlot2)==0))
        {
          if (input$replyGraph2=="Dot plot")
          {
            gp <- ggplot(data=vowelSub2(), aes(x=vowelSub2()[,1], y=vowelSub2()[,2], group=1)) +
                  geom_point(colour="indianred2", size=3) +
                  geom_errorbar(colour="indianred2", aes(ymin=ll, ymax=ul), width=w) +
                  ggtitle(input$title2) +
                  xlab(paste(input$replyXaxis2, collapse = " ")) + ylab("duration") +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint2b), family=input$replyFont2b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        aspect.ratio   =0.67)
          }
          else

          if (input$replyGraph2=="Bar chart")
          {
            gp <- ggplot(data=vowelSub2(), aes(x=vowelSub2()[,1], y=vowelSub2()[,2])) +
                  geom_bar(stat="identity", colour="black", fill="indianred2", size=.3) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w) +
                  ggtitle(input$title2) +
                  xlab(paste(input$replyXaxis2, collapse = " ")) + ylab("duration") +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint2b), family=input$replyFont2b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        aspect.ratio   =0.67)
          }
        }
        else

        if (((length(input$catLine2)==0) | (length(input$catLine2)>14)) && (length(input$catPlot2)>0))
        {
          if (input$replyGraph2=="Dot plot")
          {
            gp <- ggplot(data=vowelSub2(), aes(x=vowelSub2()[,1], y=vowelSub2()[,3], group=1)) +
                  geom_point(colour="indianred2", size=3) +
                  geom_errorbar(colour="indianred2", aes(ymin=ll, ymax=ul), width=w) +
                  ggtitle(input$title2) +
                  xlab(paste(input$replyXaxis2, collapse = " ")) + ylab("duration") +
                  facet_wrap(~vowelSub2()[,2]) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint2b), family=input$replyFont2b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        aspect.ratio   =0.67)
          }

          else

          if (input$replyGraph2=="Bar chart")
          {
            gp <- ggplot(data=vowelSub2(), aes(x=vowelSub2()[,1], y=vowelSub2()[,3])) +
                  geom_bar(stat="identity", colour="black", fill="indianred2", size=.3) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w) +
                  ggtitle(input$title2) +
                  xlab(paste(input$replyXaxis2, collapse = " ")) + ylab("duration") +
                  facet_wrap(~vowelSub2()[,2]) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint2b), family=input$replyFont2b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        aspect.ratio   =0.67)
          }
          else {}
        }
        else

        if (((length(input$catLine2)>0) & (length(input$catLine2)<=14)) && (length(input$catPlot2)==0))
        {
          if (input$replyGraph2=="Dot plot")
          {
            pd <- position_dodge(0.7)

            gp <- ggplot(data=vowelSub2(), aes(x=vowelSub2()[,1], y=vowelSub2()[,3], group=vowelSub2()[,2], color=vowelSub2()[,2])) +
                  geom_point(size=3, position=pd) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w, position=pd) +
                  ggtitle(input$title2) +
                  xlab(paste(input$replyXaxis2, collapse = " ")) + ylab("duration") +
                  scale_colour_discrete(name=paste0(paste(input$replyLine2, collapse = " "),"\n")) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint2b), family=input$replyFont2b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        legend.key.size=unit(1.5, 'points'),
                        aspect.ratio   =0.67)
          }
          else

          if (input$replyGraph2=="Bar chart")
          {
            pd <- position_dodge(0.9)

            gp <- ggplot(data=vowelSub2(), aes(x=vowelSub2()[,1], y=vowelSub2()[,3], fill=vowelSub2()[,2])) +
                  geom_bar(position=position_dodge(), stat="identity", colour="black", size=.3) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w, position=pd) +
                  ggtitle(input$title2) +
                  xlab(paste(input$replyXaxis2, collapse = " ")) + ylab("duration") +
                  scale_fill_hue(name=paste0(paste(input$replyLine2, collapse = " "),"\n")) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint2b), family=input$replyFont2b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        legend.key.size=unit(1.5, 'lines'),
                        aspect.ratio   =0.67)
          }
          else {}

          if (input$replyXaxis2==input$replyLine2)
            gp <- gp + theme(axis.title.x=element_blank(),
                             axis.text.x =element_blank(),
                             axis.ticks.x=element_blank())
          else {}
        }
        else

        if (((length(input$catLine2)>0) & (length(input$catLine2)<=14))  && (length(input$catPlot2)>0))
        {
          if (input$replyGraph2=="Dot plot")
          {
            pd <- position_dodge(0.5)

            gp <- ggplot(data=vowelSub2(), aes(x=vowelSub2()[,1], y=vowelSub2()[,4], group=vowelSub2()[,2], color=vowelSub2()[,2])) +
                  geom_point(size=3, position=pd) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w, position=pd) +
                  ggtitle(input$title2) +
                  xlab(paste(input$replyXaxis2, collapse = " ")) + ylab("duration") +
                  scale_colour_discrete(name=paste0(paste(input$replyLine2, collapse = " "),"\n")) +
                  facet_wrap(~vowelSub2()[,3]) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint2b), family=input$replyFont2b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        legend.key.size=unit(1.5, 'points'),
                        aspect.ratio   =0.67)
          }
          else

          if (input$replyGraph2=="Bar chart")
          {
            pd <- position_dodge(0.9)

            gp <- ggplot(data=vowelSub2(), aes(x=vowelSub2()[,1], y=vowelSub2()[,4], fill=vowelSub2()[,2])) +
                  geom_bar(position=position_dodge(), stat="identity", colour="black", size=.3) +
                  geom_errorbar(aes(ymin=ll, ymax=ul), width=w, position=pd) +
                  ggtitle(input$title2) +
                  xlab(paste(input$replyXaxis2, collapse = " ")) + ylab("duration") +
                  scale_fill_hue(name=paste0(paste(input$replyLine2, collapse = " "),"\n")) +
                  facet_wrap(~vowelSub2()[,3]) +
                  theme_bw() +
                  theme(text           =element_text(size=as.numeric(input$replyPoint2b), family=input$replyFont2b),
                        axis.text.x    =element_text(angle=Angle),
                        plot.title     =element_text(face="bold", hjust = 0.5),
                        legend.key.size=unit(1.5, 'lines'),
                        aspect.ratio   =0.67)
          }
          else {}

          if (input$replyXaxis2==input$replyLine2)
            gp <- gp + theme(axis.title.x=element_blank(),
                             axis.text.x =element_blank(),
                             axis.ticks.x=element_blank())
          else {}
        }
        else {}

        return(graphics::plot(gp))
      }

      res2 <- function()
      {
        if (length(input$replySize2b)==0)
          return( 72)
        
        if (input$replySize2b=="tiny"  )
          return( 54)
        if (input$replySize2b=="small" )
          return( 72)
        if (input$replySize2b=="normal")
          return( 90)
        if (input$replySize2b=="large" )
          return(108)
        if (input$replySize2b=="huge"  )
          return(144)
      }

      observeEvent(input$replySize2b,
      {
        output$graph2 <- renderPlot(height = 550, width = 700, res = res2(),
        {
          if (length(input$catXaxis2)>0)
          {
            plotGraph2()
          }
        })
      })

      output$Graph2 <- renderUI(
      {
        plotOutput("graph2", height="627px")
      })

      output$selFormat2a <- renderUI(
      {
        options <- c("txt","xlsx")
        selectInput('replyFormat2a', label=NULL, options, selected = options[2], selectize=FALSE, multiple=FALSE)
      })

      fileName2a <- function()
      {
        return(paste0("durationTable.",input$replyFormat2a))
      }

      output$download2a <- downloadHandler(filename = fileName2a, content = function(file)
      {
        if (length(input$catXaxis2)>0)
        {
          vT <- vowelSub2()

          colnames(vT)[which(colnames(vT)=="sd")] <- "standard deviation"
          colnames(vT)[which(colnames(vT)=="se")] <- "standard error"
          colnames(vT)[which(colnames(vT)=="n" )] <- "number of observations"
          colnames(vT)[which(colnames(vT)=="ll")] <- "lower limit"
          colnames(vT)[which(colnames(vT)=="ul")] <- "upper limit"
        }
        else
          vT <- data.frame()

        if (input$replyFormat2a=="txt")
        {
          utils::write.table(vT, file, sep = "\t", na = "NA", dec = ".", row.names = FALSE, col.names = TRUE)
        }
        else

        if (input$replyFormat2a=="xlsx")
        {
          WriteXLS(vT, file, SheetNames = "table", row.names=FALSE, col.names=TRUE, BoldHeaderRow = TRUE, na = "NA", FreezeRow = 1, AdjWidth = TRUE)
        }
        else {}
      })

      output$selSize2b <- renderUI(
      {
        options <- c("tiny", "small", "normal", "large", "huge")
        selectInput('replySize2b', label=NULL, options, selected = options[3], selectize=FALSE, multiple=FALSE)
      })

      output$selFont2b <- renderUI(
      {
        options <- c("Courier" = "Courier", "Helvetica" = "Helvetica", "Times" = "Times")
        selectInput('replyFont2b', label=NULL, options, selected = "Helvetica", selectize=FALSE, multiple=FALSE)
      })

      output$selPoint2b <- renderUI(
      {
        options <- c(5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,36,40,44,48)
        selectInput('replyPoint2b', label=NULL, options, selected = 18, selectize=FALSE, multiple=FALSE)
      })

      output$selFormat2b <- renderUI(
      {
        options <- c("JPG","PNG","SVG","EPS","PDF","TEX")
        selectInput('replyFormat2b', label=NULL, options, selected = "PNG", selectize=FALSE, multiple=FALSE)
      })

      fileName2b <- function()
      {
        return(paste0("durationPlot.",input$replyFormat2b))
      }

      output$download2b <- downloadHandler(filename = fileName2b, content = function(file)
      {
        grDevices::pdf(NULL)

        scale  <- 72/res2()
        width  <- convertUnit(x=unit(700, "pt"), unitTo="in", valueOnly=TRUE)
        height <- convertUnit(x=unit(550, "pt"), unitTo="in", valueOnly=TRUE)
        
        if ((length(input$catXaxis2)>0) && (nrow(vowelSub2())>0))
          plot <- plotGraph2()
        else
          plot <- ggplot()+theme_bw()
        
        show_modal_spinner()
        
        if (input$replyFormat2b=="JPG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="jpeg")
        else
        if (input$replyFormat2b=="PNG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="png" )
        else
        if (input$replyFormat2b=="SVG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="svg" )
        else
        if (input$replyFormat2b=="EPS")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_ps )
        else
        if (input$replyFormat2b=="PDF")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_pdf)
        else    
        if (input$replyFormat2b=="TEX")
        {
          tikzDevice::tikz(file=file, width=width, height=height, engine='xetex')
          print(plot)
        }
        else {}

        grDevices::graphics.off()
        
        remove_modal_spinner()
      })

      ##########################################################################

      replyTimes30  <- reactive(input$replyTimes3)
      replyTimes3   <- debounce(replyTimes30 , 2000)

      replyTimesN30 <- reactive(input$replyTimesN3)
      replyTimesN3  <- debounce(replyTimesN30, 2000)

      selFormant30  <- reactive(input$selFormant3)
      selFormant3   <- debounce(selFormant30 , 2000)

      vowelScale3 <- reactive(
      {
        return(vowelScale(vowelSame(),input$replyScale3,0))
      })

      vowelNorm3 <- reactive(
      {
        if (length(input$replyNormal3)==0)
          return(NULL)

        if (!is.null(replyTimesN3()))
          replyTimesN <- replyTimesN3()
        else
          return(NULL)

        indexDuration <- grep("^duration$", tolower(colnames(vowelScale3())))
        nPoints       <- (ncol(vowelScale3()) - indexDuration) / 5

        if (max(replyTimesN) > nPoints)
          replyTimesN <- Round(nPoints/2)
        else {}
        
        vL1 <- vowelLong1(vowelScale1(),replyTimesN)
        vL2 <- vowelLong2(vL1)
        vL3 <- vowelLong3(vL1)
        vL4 <- vowelLong4(vL1)
        vLD <- vowelLongD(vL1)
    
        return(vowelNormF(vowelScale3(), vL1, vL2, vL3, vL4, vLD, input$replyNormal3))
      })

      vowelSubS3 <- reactive(
      {
        if (is.null(vowelTab()) || (nrow(vowelTab())==0))
          return(NULL)
        
        if (length(vowelSame()$vowel)==0)
        {
          showNotification("There are no sounds shared by all speakers!", type = "error", duration = 30)
          return(NULL)
        }
        
        if ((((input$selMetric3=="Euclidean") & (length(input$replyVowel3)<1)) | ((input$selMetric3=="Accdist") & (length(input$replyVowel3)<3))) || (length(replyTimes3())==0) || (length(selFormant3())==0))
          return(NULL)

        req(vowelNorm3())
        
        vT <- vowelNorm3()
        vT <- subset(vT, is.element(vT$vowel, input$replyVowel3))

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        nPoints <- (ncol(vowelTab()) - (indexVowel + 1))/5

        if ((nrow(vT)>0) && (max(as.numeric(replyTimes3()))<=nPoints))
        {
          vT0 <- data.frame()

          for (i in (1:length(replyTimes3())))
          {
            Code <- strtoi(replyTimes3()[i])

            indexF1 <- indexVowel + 4 + ((Code-1) * 5)
            indexF2 <- indexVowel + 5 + ((Code-1) * 5)
            indexF3 <- indexVowel + 6 + ((Code-1) * 5)

            vT0 <- rbind(vT0, data.frame(vowel   = vT$vowel    ,
                                         speaker = vT$speaker  ,
                                         time    = i           ,
                                         F1      = vT[,indexF1],
                                         F2      = vT[,indexF2],
                                         F3      = vT[,indexF3]))
          }

          return(stats::aggregate(cbind(F1,F2,F3)~vowel+speaker+time, data=vT0, FUN=mean))
        }
        else
          return(data.frame())
      })

      vowelSubG3 <- reactive(
      {
        if (is.null(vowelSubS3()) || (nrow(vowelSubS3())==0) || (length(input$replyGrouping3)==0))
          return(NULL)

        vT0 <- unique(data.frame(speaker=vowelNorm3()$speaker,grouping=fuseCols(vowelNorm3(),input$replyGrouping3)))

        if (max(as.data.frame(table(vT0$speaker))$Freq)==1)
        {
          rownames(vT0) <- vT0$speaker
          vT0$speaker <- NULL
          vT <- vT0[as.character(vowelSubS3()$speaker),]
        }
        else
          vT <- rep("none",nrow(vowelSubS3()))

        return(data.frame(grouping=vT))
      })

      # Distances among speakers

      vowelCorS1 <- reactive(
      {
        if (is.null(vowelSubS3()) || (nrow(vowelSubS3())==0))
          return(NULL)

        vT <- vowelSubS3()
        vT$speaker <- as.character(vT$speaker)

        labs <- unique(vT$speaker)
        nl <- length(labs)

        corr <- matrix(0, nrow = nl, ncol = nl)

        rownames(corr) <- labs
        colnames(corr) <- labs

        withProgress(value = 0, style = "old",
        {
          for (i in (2:nl))
          {
            incProgress(1/nl, message = paste("Calculating ...", format(round2((i/nl)*100)), "%"))

            iSub <- subset(vT, speaker==labs[i])

            for (j in (1:(i-1)))
            {
              jSub <- subset(vT, speaker==labs[j])

              if (is.element("F1",selFormant3()))
                difF1 <- (iSub$F1-jSub$F1)^2
              if (is.element("F2",selFormant3()))
                difF2 <- (iSub$F2-jSub$F2)^2
              if (is.element("F3",selFormant3()))
                difF3 <- (iSub$F3-jSub$F3)^2

              sumF <- rep(0, nrow(iSub))

              if (is.element("F1",selFormant3()))
                sumF <- sumF + difF1
              if (is.element("F2",selFormant3()))
                sumF <- sumF + difF2
              if (is.element("F3",selFormant3()))
                sumF <- sumF + difF3

              sumF <- sqrt(sumF)

              corr[i,j] <- mean(sumF)
              corr[j,i] <- corr[i,j]
            }
          }
        })

        for (i in (1:nl))
        {
          corr[i,i] <- 0
        }

        return(corr)
      })

      # Measure distances among vowels per speaker

      vDist <- function(vTsub)
      {
        vTsub$voweltime <- paste(vTsub$vowel,vTsub$time)

        labs <- unique(vTsub$voweltime)
        nl <- length(labs)

        vec <- c()
        k <- 0

        for (i in (2:nl))
        {
          iSub <- subset(vTsub, voweltime==labs[i])

          for (j in (1:(i-1)))
          {
            jSub <- subset(vTsub, voweltime==labs[j])

            k <- k + 1

            sqsum <- 0
            if (is.element("F1",selFormant3()))
              sqsum <- sqsum + (iSub$F1-jSub$F1)^2
            if (is.element("F2",selFormant3()))
              sqsum <- sqsum + (iSub$F2-jSub$F2)^2
            if (is.element("F3",selFormant3()))
              sqsum <- sqsum + (iSub$F3-jSub$F3)^2

            vec[k] <- sqrt(sqsum)
          }
        }

        return(vec)
      }

      # Measure within distances for all speakers

      wDist <- function(vT)
      {
        labs <- unique(vT$speaker)
        nl <- length(labs)

        spk <- c()
        vec <- c()

        withProgress(value = 0, style = "old",
        {
          for (i in (1:nl))
          {
            incProgress(1/nl, message = paste("Calculating part 1/2 ...", format(round2((i/nl)*100)), "%"))

            iSub <- subset(vT, speaker==labs[i])
            iVec <- vDist(iSub)

            spk <- c(spk,rep(labs[i],length(iVec)))
            vec <- c(vec,iVec)
          }
        })

        return(data.frame(speaker=spk,dist=vec))
      }

      # Correlations among speakers

      vowelCorS2 <- reactive(
      {
        if (is.null(vowelSubS3()) || (nrow(vowelSubS3())==0))
          return(NULL)

        vT <- vowelSubS3()
        vT$speaker <- as.character(vT$speaker)

        vec <- wDist(vT)

        labs <- unique(vec$speaker)
        nl <- length(labs)

        corr <- matrix(0, nrow = nl, ncol = nl)

        rownames(corr) <- labs
        colnames(corr) <- labs

        withProgress(value = 0, style = "old",
        {
          for (i in (2:nl))
          {
            incProgress(1/nl, message = paste("Calculating part 2/2 ...", format(round2((i/nl)*100)), "%"))

            iVec <- subset(vec, speaker==labs[i])$dist

            for (j in (1:(i-1)))
            {
              jVec <- subset(vec, speaker==labs[j])$dist

              if ((stats::sd(iVec)>0) && (stats::sd(jVec)>0))
              {
                corr[i,j] <- stats::cor(iVec,jVec)
              }
              else
                corr[i,j] <- 0

              corr[j,i] <- corr[i,j]
            }
          }
        })

        for (i in (1:nl))
        {
          corr[i,i] <- 1
        }

        return(corr)
      })

      # Compare speakers

      vowelCorS <- reactive(
      {
        if (input$selMetric3=="Euclidean")
          return(vowelCorS1())

        if (input$selMetric3=="Accdist")
          return(vowelCorS2())
      })

      # Correlations among speakers of selected groupings

      vowelCorC <- reactive(
      {
        if (is.null(vowelSubG3()) || (nrow(vowelSubG3())==0))
          return(NULL)

        vT <- data.frame(speaker=vowelSubS3()$speaker,grouping=vowelSubG3())
        vT <- subset(vT, is.element(vT$grouping,input$catGrouping3))
        vT$speaker <- as.character(vT$speaker)

        labs <- unique(vT$speaker)

        if (length(labs)<3)
          return(NULL)

        return(vowelCorS()[labs,labs])
      })

      # Correlations among groupings

      vowelCorG <- reactive(
      {
        if (is.null(vowelSubG3()) || (nrow(vowelSubG3())==0))
          return(NULL)

        vT <- data.frame(speaker=vowelSubS3()$speaker,grouping=vowelSubG3())
        vT <- subset(vT, is.element(vT$grouping,input$catGrouping3))
        vT$speaker <- as.character(vT$speaker)

        labs <- unique(vT$grouping)
        nl <- length(labs)

        if (length(labs)<3)
          return(NULL)

        corr <- matrix(0, nrow = nl, ncol = nl)

        rownames(corr) <- labs
        colnames(corr) <- labs

        for (i in (2:nl))
        {
          iSub  <- subset(vT, grouping==labs[i])
          iLabs <- unique(iSub$speaker)

          for (j in (1:(i-1)))
          {
            jSub  <- subset(vT, grouping==labs[j])
            jLabs <- unique(jSub$speaker)

            subVowelCorC <- vowelCorC()[iLabs,jLabs]

            corr[i,j] <- mean(subVowelCorC)
            corr[j,i] <- corr[i,j]
          }
        }

        for (i in (1:nl))
          corr[i,i] <- 1

        return(corr)
      })

      # Measure correlations

      vowelCor3 <- reactive(
      {
        if (is.null(vowelSubS3()) || (nrow(vowelSubS3())==0) || is.null(vowelSubG3()) || (nrow(vowelSubG3())==0))
          return(NULL)

        if (!input$summarize3)
          vowelCor <- vowelCorC()
        else
          vowelCor <- vowelCorG()

        if (!is.null(vowelCor) && (nrow(vowelCor)>0) && (stats::sd(vowelCor)>0))
          return(vowelCor)
        else
          return(NULL)
      })

      # Measure distances

      vowelDiff3 <- reactive(
      {
        if (is.null(vowelCor3()) || (nrow(vowelCor3())==0))
          return(NULL)

        if (input$selMetric3=="Euclidean")
          return(  vowelCor3())

        if (input$selMetric3=="Accdist"  )
          return(1-vowelCor3())
      })

      vowelDist3 <- reactive(
      {
        if (is.null(vowelDiff3()) || (nrow(vowelDiff3())==0))
          return(NULL)
        else
          return(stats::as.dist(vowelDiff3(), diag=FALSE, upper=FALSE))
      })

      clusObj <- reactive(
      {
        if (input$replyMethod31=="S-L")
          clus <- stats::hclust(vowelDist3(), method="single")

        if (input$replyMethod31=="C-L")
          clus <- stats::hclust(vowelDist3(), method="complete")

        if (input$replyMethod31=="UPGMA")
          clus <- stats::hclust(vowelDist3(), method="average")

        if (input$replyMethod31=="WPGMA")
          clus <- stats::hclust(vowelDist3(), method="mcquitty")

        if (input$replyMethod31=="Ward")
          clus <- stats::hclust(vowelDist3(), method="ward.D2")

        return(clus)
      })

      getPerplexity <- function()
      {
        if (nrow(vowelCor3()) < 91)
          return((nrow(vowelCor3())-1) %/% 3)
        else
          return(30)
      }

      multObj <- reactive(
      {
        if (input$replyMethod32=="Classical")
        {
          fit <- stats::cmdscale(vowelDist3(), eig=TRUE, k=2)
          coords <- as.data.frame(fit$points)
        }

        if (input$replyMethod32=="Kruskal's")
        {
          fit <- isoMDS(vowelDist3(), k=2)
          coords <- as.data.frame(fit$points)
        }

        if (input$replyMethod32=="Sammon's")
        {
          fit <- sammon(vowelDist3(), k=2)
          coords <- as.data.frame(fit$points)
        }

        if (input$replyMethod32=="t-SNE")
        {
          fit <- Rtsne(vowelDist3(), check_duplicates=FALSE, pca=TRUE, perplexity=getPerplexity(), theta=0.5, dims=2)
          coords <- as.data.frame(fit$Y)
        }

        return(coords)
      })

      output$selTimes3 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        showExclVow()

        timeCode     <- getTimeCode()
        indexVowel   <- grep("^vowel$", colnames(vowelTab()))
        nColumns     <- ncol(vowelTab())
        nPoints      <- (nColumns - (indexVowel + 1))/5

        checkboxGroupInput('replyTimes3', 'Time points to be included:', timeCode, selected = Round(nPoints/2), TRUE)
      })

      output$selScale3 <- renderUI(
      {
        selectInput('replyScale3', 'Scale:', optionsScale()[1:(length(optionsScale())-1)], selected = optionsScale()[1], selectize=FALSE, multiple=FALSE, width="100%")
      })

      output$selNormal3 <- renderUI(
      {
        if (is.null(vowelTab()) || length(input$replyScale3)==0)
          return(NULL)

        onlyF1F2 <- !is.element("F3", input$selFormant3)
        selectInput('replyNormal3', 'Normalization:', optionsNormal(vowelTab(), input$replyScale3, TRUE, onlyF1F2), selected = optionsNormal(vowelTab(), input$replyScale3, TRUE, onlyF1F2)[1], selectize=FALSE, multiple=FALSE)
      })

      output$selTimesN3 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if ((length(input$replyNormal3)>0) && ((input$replyNormal3=="") |
                                               (input$replyNormal3==" Peterson") |
                                               (input$replyNormal3==" Sussman") |
                                               (input$replyNormal3==" Syrdal & Gopal") |
                                               (input$replyNormal3==" Thomas & Kendall")))
          return(NULL)

        timeCode     <- getTimeCode()
        indexVowel   <- grep("^vowel$", colnames(vowelTab()))
        nColumns     <- ncol(vowelTab())
        nPoints      <- (nColumns - (indexVowel + 1))/5

        checkboxGroupInput('replyTimesN3', 'Normalization based on:', timeCode, selected = Round(nPoints/2), TRUE)
      })

      output$selVowel3 <- renderUI(
      {
        if (is.null(vowelSame()))
          return(NULL)

        options <- unique(vowelSame()$vowel)
        selectInput('replyVowel3', 'Sel. vowels:', options, multiple=TRUE, selectize = FALSE, size=4, width="100%")
      })

      output$selGrouping3 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        options <- c(colnames(vowelTab()[1:(indexVowel-1)]))

        selectInput('replyGrouping3', 'Sel. variable:', options, selected = character(0), multiple=TRUE, selectize=FALSE, size=4, width="100%")
      })

      output$catGrouping3 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$replyGrouping3)>0)
          options <- unique(fuseCols(vowelTab(),input$replyGrouping3))
        else
          options <- NULL

        selectInput('catGrouping3', 'Sel. categories:', options, multiple=TRUE, selectize = FALSE, size=4, width="100%")
      })

      output$selMethod3 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if (length(input$selClass3)>0)
        {
          if (input$selClass3=="Cluster analysis")
            radioButtons('replyMethod31', NULL, c("S-L","C-L","UPGMA","WPGMA","Ward"), selected="UPGMA"    , TRUE)
          else
          if (input$selClass3=="Multidimensional scaling")
            radioButtons('replyMethod32', NULL, c("Classical","Kruskal's","Sammon's","t-SNE"), selected="Classical", TRUE)
          else {}
        }
        else
          return(NULL)
      })

      output$selGeon3 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        if ((length(input$selClass3)>0) && (input$selClass3=="Multidimensional scaling"))
          checkboxGroupInput("mdsGeon3", "Options: ", c("points","labels","X\u21C4Y","inv. X","inv. Y"), selected=c("points","labels"), inline=TRUE)
        else
          return(NULL)
      })

      colPalette3 <- function(n)
      {
        return(colPalette(n,input$grayscale3))
      }

      plotClus <- function()
      {
        dendro <- dendro_data(stats::as.dendrogram(clusObj()), type = "rectangle")

        gp <- ggplot(dendro$segments) +
              geom_segment(aes(x = x, y = y, xend = xend, yend = yend))

        speakers         <- as.character(label(dendro)$label)
        lookup           <- unique(data.frame(speaker=vowelNorm3()$speaker,grouping=fuseCols(vowelNorm3(),input$replyGrouping3)))
        rownames(lookup) <- lookup$speaker
        lookup$speaker   <- NULL
        groupings        <- lookup[speakers,]

        if (length(speakers)>90) fs <- 0.7 else
        if (length(speakers)>75) fs <- 1   else
        if (length(speakers)>60) fs <- 2   else
        if (length(speakers)>45) fs <- 3   else
        if (length(speakers)>30) fs <- 4   else
        if (length(speakers)>15) fs <- 5   else
        if (length(speakers)> 1) fs <- 6   else {}

        fs <- min(fs, convertUnit(unit(as.numeric(input$replyPoint3b), "pt"), "mm", valueOnly=TRUE))

        dendro$labels$label <- paste0("  ",dendro$labels$label)

        if ((input$replyGrouping3=="speaker") || (length(unique(groupings))==1) || (input$summarize3))
          gp <- gp + geom_text (data = dendro$labels, aes(x, y, label = label                  ), hjust = 0, angle = 0, family=input$replyFont3b, size = fs)
        else
          gp <- gp + geom_text (data = dendro$labels, aes(x, y, label = label, colour=groupings), hjust = 0, angle = 0, family=input$replyFont3b, size = fs)

        gp <- gp +
              scale_y_reverse(expand = c(0.5, 0)) +
              scale_color_manual(values=colPalette3(length(unique(groupings)))) +
              labs(colour=paste0(" ",paste(input$replyGrouping3, collapse = " "),"\n")) +
              coord_flip() +
              ggtitle(input$title3) +
              xlab(NULL) + ylab(NULL) +
              theme_bw() +
              theme(text            =element_text(size=as.numeric(input$replyPoint3b), family=input$replyFont3b),
                    plot.title      =element_text(face="bold", hjust = 0.5),
                    axis.text       =element_blank(),
                    axis.ticks      =element_blank(),
                    panel.grid.major=element_blank(),
                    panel.grid.minor=element_blank(),
                    legend.key.size =unit(1.5,'lines')) +
              guides(color = guide_legend(override.aes = list(linetype = 0, shape=3)))

        print(gp)
      }

      plotMult <- function()
      {
        coords <- multObj()

        if (!is.element("X\u21C4Y", input$mdsGeon3))
        {
          Xlab <- "dimension 1"
          Ylab <- "dimension 2"
        }
        else
        {
          colnames(coords)[1] <- "V0"
          colnames(coords)[2] <- "V1"
          colnames(coords)[1] <- "V2"

          Xlab <- "dimension 2"
          Ylab <- "dimension 1"
        }

        if (is.element("inv. X", input$mdsGeon3))
          coords$V1 <- -1 * coords$V1

        if (is.element("inv. Y", input$mdsGeon3))
          coords$V2 <- -1 * coords$V2

        speakers         <- as.character(rownames(as.matrix(vowelDist3())))
        lookup           <- unique(data.frame(speaker=vowelNorm3()$speaker,grouping=fuseCols(vowelNorm3(),input$replyGrouping3)))
        rownames(lookup) <- lookup$speaker
        lookup$speaker   <- NULL
        groupings        <- lookup[speakers,]

        if (length(speakers)>45) fs <- 3 else
        if (length(speakers)>30) fs <- 4 else
        if (length(speakers)>10) fs <- 5 else
        if (length(speakers)> 1) fs <- 6 else {}

        fs <- min(fs, convertUnit(unit(as.numeric(input$replyPoint3b), "pt"), "mm", valueOnly=TRUE))

        if ((input$replyGrouping3=="speaker") || (length(unique(groupings))==1) || (input$summarize3))
          gp <- ggplot(coords, aes(V1, V2, label = rownames(as.matrix(vowelDist3()))                 ))
        else
          gp <- ggplot(coords, aes(V1, V2, label = rownames(as.matrix(vowelDist3())), color=groupings))

        if (is.element("points", input$mdsGeon3) &
            is.element("labels", input$mdsGeon3))
          gp <- gp + geom_point(size = 2.0) + geom_text_repel(family=input$replyFont3b, size = fs, show.legend=FALSE, max.overlaps=100)
        else

        if (is.element("points", input$mdsGeon3))
          gp <- gp + geom_point(size = 2.5)
        else

        if (is.element("labels", input$mdsGeon3))
          gp <- gp + geom_text (family=input$replyFont3b, size = fs, family=input$replyFont3b)
        else {}

        gp <- gp +
              scale_x_continuous(breaks = 0, limits = c(min(coords$V1-0.01,coords$V2-0.01), max(coords$V1+0.01,coords$V2+0.01))) +
              scale_y_continuous(breaks = 0, limits = c(min(coords$V1-0.01,coords$V2-0.01), max(coords$V1+0.01,coords$V2+0.01))) +
              scale_color_manual(values=colPalette3(length(unique(groupings)))) +
              labs(colour=paste0(" ",paste(input$replyGrouping3, collapse = " "),"\n")) +
              geom_vline(xintercept = 0, color="darkgrey") + geom_hline(yintercept = 0, color="darkgrey") +
              ggtitle(input$title3) +
              xlab(Xlab) + ylab(Ylab) +
              theme_bw() +
              theme(text           =element_text(size=as.numeric(input$replyPoint3b), family=input$replyFont3b),
                    plot.title     =element_text(face="bold", hjust = 0.5),
                    legend.key.size=unit(1.5,'lines'),
                    aspect.ratio   =1)

        print(gp)
      }

      plotGraph3 <- function()
      {
        if (is.null(vowelDist3()))
          return(NULL)

        if (length(input$selClass3)>0)
        {
          if ((length(input$replyMethod31)>0) && (input$selClass3=="Cluster analysis"        ))
            plotClus()
          else

          if ((length(input$replyMethod32)>0) && (input$selClass3=="Multidimensional scaling"))
            plotMult()
          else {}
        }
        else
          return(NULL)
      }

      res3 <- function()
      {
        if (length(input$replySize3b)==0)
          return( 72)
        
        if (input$replySize3b=="tiny"  )
          return( 54)
        if (input$replySize3b=="small" )
          return( 72)
        if (input$replySize3b=="normal")
          return( 90)
        if (input$replySize3b=="large" )
          return(108)
        if (input$replySize3b=="huge"  )
          return(144)
      }

      observeEvent(input$replySize3b,
      {
        output$graph3 <- renderPlot(height = 550, width = 700, res = res3(),
        {
          if ((((input$selMetric3=="Euclidean") & (length(input$replyVowel3)>=1)) | ((input$selMetric3=="Accdist") & (length(input$replyVowel3)>=3))) && (length(input$replyGrouping3)>0) && (length(input$catGrouping3)>0) && (length(replyTimes3())>0) && (length(selFormant3())>0) && (!is.null(vowelCor3())))
          {
            plotGraph3()
          }
        })
      })

      output$Graph3 <- renderUI(
      {
        plotOutput("graph3", height="627px")
      })

      mdsDist <- function(coords)
      {
        nf <- nrow(coords)
        dist <- matrix(0, nrow = nf, ncol = nf)

        nd <- ncol(coords)

        for (i in 2:nf)
        {
          for (j in 1:(i-1))
          {
            sum <- 0

            for (k in 1:nd)
            {
              sum <- sum + (coords[i,k] - coords[j,k])^2
            }

            dist[i,j] <- sqrt(sum)
            dist[j,i] <- sqrt(sum)
          }
        }

        for (i in 1:nf)
          dist[i,i] <- 0

        return(stats::as.dist(dist, diag=FALSE, upper=FALSE))
      }

      output$explVar3 <- renderUI(
      {
        if (is.null(vowelDist3()))
          return(NULL)

        if (length(input$selClass3)>0)
        {
          if ((length(input$replyMethod31)>0) && (input$selClass3=="Cluster analysis"))
          {
            explVar <- formatC(x=stats::cor(vowelDist3(), stats::cophenetic(clusObj()))^2, digits = 4, format = "f")
            return(tags$div(HTML(paste0("<font color='black'>","Explained variance: ",explVar,"</font><br><br>"))))
          }
          else

          if ((length(input$replyMethod32)>0) && (input$selClass3=="Multidimensional scaling"))
          {
            explVar <- formatC(x=stats::cor(vowelDist3(), mdsDist(multObj()))^2, digits = 4, format = "f")
            return(tags$div(HTML(paste0("<font color='black'>","Explained variance: ",explVar,"</font><br><br>"))))
          }
          else {}
        }
        else
          return(NULL)
      })

      output$selFormat3a <- renderUI(
      {
        options <- c("txt","xlsx")
        selectInput('replyFormat3a', label=NULL, options, selected = options[2], selectize=FALSE, multiple=FALSE)
      })

      fileName3a <- function()
      {
        return(paste0("exploreTable.",input$replyFormat3a))
      }

      output$download3a <- downloadHandler(filename = fileName3a, content = function(file)
      {
        if ((((input$selMetric3=="Euclidean") & (length(input$replyVowel3)>=1)) | ((input$selMetric3=="Accdist") & (length(input$replyVowel3)>=3))) && (length(input$replyGrouping3)>0) && (length(input$catGrouping3)>0) && (length(replyTimes3())>0) && (length(selFormant3())>0)  && (!is.null(vowelDiff3())))
        {
          vT <- data.frame(rownames(vowelDiff3()), vowelDiff3())
          colnames(vT) <- c("element", colnames(vowelDiff3()))
        }
        else
          vT <- data.frame()

        if (input$replyFormat3a=="txt")
        {
          utils::write.table(vT, file, sep = "\t", na = "NA", dec = ".", row.names = FALSE, col.names = TRUE)
        }
        else

        if (input$replyFormat3a=="xlsx")
        {
          WriteXLS(vT, file, SheetNames = "table", row.names=FALSE, col.names=TRUE, na = "NA", FreezeRow = 1, FreezeCol = 1, AdjWidth = TRUE)
        }
        else {}
      })

      output$selSize3b <- renderUI(
      {
        options <- c("tiny", "small", "normal", "large", "huge")
        selectInput('replySize3b', label=NULL, options, selected = options[3], selectize=FALSE, multiple=FALSE)
      })

      output$selFont3b <- renderUI(
      {
        options <- c("Courier" = "Courier", "Helvetica" = "Helvetica", "Times" = "Times")
        selectInput('replyFont3b', label=NULL, options, selected = "Helvetica", selectize=FALSE, multiple=FALSE)
      })

      output$selPoint3b <- renderUI(
      {
        options <- c(5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,36,40,44,48)
        selectInput('replyPoint3b', label=NULL, options, selected = 18, selectize=FALSE, multiple=FALSE)
      })

      output$selFormat3b <- renderUI(
      {
        options <- c("JPG","PNG","SVG","EPS","PDF","TEX")
        selectInput('replyFormat3b', label=NULL, options, selected = "PNG", selectize=FALSE, multiple=FALSE)
      })

      fileName3b <- function()
      {
        return(paste0("explorePlot.",input$replyFormat3b))
      }

      output$download3b <- downloadHandler(filename = fileName3b, content = function(file)
      {
        grDevices::pdf(NULL)

        scale  <- 72/res3()
        width  <- convertUnit(x=unit(700, "pt"), unitTo="in", valueOnly=TRUE)
        height <- convertUnit(x=unit(550, "pt"), unitTo="in", valueOnly=TRUE)
        
        if ((length(input$replyVowel3)>=3) && (length(input$replyGrouping3)>0) && (length(input$catGrouping3)>0) && (length(replyTimes3())>0) && (length(selFormant3())>0) && (!is.null(vowelDiff3())))
          plot <- plotGraph3()
        else
          plot <- ggplot()+theme_bw()
        
        show_modal_spinner()
        
        if (input$replyFormat3b=="JPG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="jpeg")
        else
        if (input$replyFormat3b=="PNG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="png" )
        else
        if (input$replyFormat3b=="SVG")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device="svg" )
        else
        if (input$replyFormat3b=="EPS")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_ps )
        else
        if (input$replyFormat3b=="PDF")
          ggsave(filename=file, plot=plot, scale=scale, width=width, height=height, units="in", dpi=300, device=grDevices::cairo_pdf)
        else    
        if (input$replyFormat3b=="TEX")
        {
          tikzDevice::tikz(file=file, width=width, height=height, engine='xetex')
          print(plot)
        }
        else {}

        grDevices::graphics.off()
        
        remove_modal_spinner()
      })
      
      ##########################################################################
      
      global <- reactiveValues(replyScale5=NULL, replyNormal5=NULL)

      observeEvent(input$buttonHelp5, {
        showModal(modalDialog(easyClose = TRUE, fade = FALSE,
          title =
          HTML(paste0("<span style='font-weight: bold; font-size: 17px;'>Evaluation of scale conversion and speaker normalization methods</span>")),

          HTML(paste0("<span style='font-size: 15px;'>

                       This tab is meant to be used in order to find the most suitable combination of a scale conversion method and a speaker normalization method for your data set.
                       Choose the settings and press the Go! button. Be prepared that running the evaluation procedures <b>may take some time</b>, depending on the size of your data set.

                       <br><br>

                       In case the speakers have pronounced different sets of vowels, the procedures are run on the basis of the set of vowels that are found across all speakers.
                       The vowels thus excluded are printed.

                       <br><br>

                       <span style='font-weight: bold;'>Evaluate</span><br>

                       The results are presented as a table where the columns represent the scale conversion methods and the rows the normalization procedures.
                       Each score is shown on a background with a color somewhere in between turquoise and yellow.
                       The more yellow the background is, the better the result.
                       Note that for some tests larger scores represent better results, and for other tests smaller scores represent better results.

                       <br><br>

                       <span style='font-weight: bold;'>Compare</span><br>

                       After having selected the option 'Compare' one can choose to compare either scale conversion methods or speaker normalization methods.
                       When comparing the scale conversion methods, the unnormalized formant measurements are used.
                       When comparing the speaker normalization methods, the raw Hz formant measurements are used.

                       <br><br>

                       For more details about the implementation of the methods that are used in this tab type <span style='font-family: monospace; font-size: 90%;'>vignette('visvow')</span> in the R console and read section 6.

                       <br><br>

                       <span style='font-weight: bold;'>References</span><br>

                       Fabricius, A., Watt, D., & Johnson, D. E. (2009). A comparison of three speaker-intrinsic vowel formant frequency normalization algorithms for sociophonetics. <i>Language Variation and Change</i>, 21(3), 413-435.
                       <br>                   
                       Adank, P., Smits, R., & Van Hout, R. (2004). A comparison of vowel normalization procedures for language variation research. <i>The Journal of the Acoustical Society of America</i>, 116(5), 3099-3107.
                       <br>

                       </span>")),

          footer = modalButton("OK")
        ))
      })

      output$selTimes5 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        showExclVow()

        timeCode     <- getTimeCode()
        indexVowel   <- grep("^vowel$", colnames(vowelTab()))
        nColumns     <- ncol(vowelTab())
        nPoints      <- (nColumns - (indexVowel + 1))/5

        checkboxGroupInput('replyTimes5', 'Time points to be included:', timeCode, selected = Round(nPoints/2), TRUE)
      })

      output$selTimesN5 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        timeCode     <- getTimeCode()
        indexVowel   <- grep("^vowel$", colnames(vowelTab()))
        nColumns     <- ncol(vowelTab())
        nPoints      <- (nColumns - (indexVowel + 1))/5

        checkboxGroupInput('replyTimesN5', 'Normalization based on:', timeCode, selected = Round(nPoints/2), TRUE)
      })

      output$selVars51 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))

        if (indexVowel > 2)
          options <- c(colnames(vowelTab()[2:(indexVowel-1)]))
        else
          options <- NULL

        selectInput('replyVars51', 'Anatomic var(s):', options, selected=character(0), multiple=TRUE, selectize=FALSE, size=5, width="100%")
      })

      output$selVars52 <- renderUI(
      {
        if (is.null(vowelTab()))
          return(NULL)

        indexVowel <- grep("^vowel$", colnames(vowelTab()))

        if (indexVowel > 2)
          options <- c(colnames(vowelTab()[2:(indexVowel-1)]))
        else
          options <- NULL

        selectInput('replyVars52', 'Socioling. var(s):', options, selected=character(0), multiple=TRUE, selectize=FALSE, size=5, width="100%")
      })

      emptyF0 <- reactive(
      {
        req(vowelTab())
        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        return(sum(vowelTab()[,indexVowel+3]==0)==nrow(vowelTab()))
      })
      
      emptyF3 <- reactive(
      {
        req(vowelTab())
        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        return(sum(vowelTab()[,indexVowel+6]==0)==nrow(vowelTab()))
      })
      
      output$selF035 <- renderUI(
      {
        if (!emptyF0() | !emptyF3())
        {
          tL <- tagList(tags$b("Include:"))
            
          if (!emptyF0() & !emptyF3())
            return(tagList(tL, splitLayout(cellWidths = 38, 
                                           checkboxInput("replyF05", "f0", value = FALSE, width = NULL),
                                           checkboxInput("replyF35", "F3", value = FALSE, width = NULL))))
          else 
              
          if (!emptyF0())
            return(tagList(tL, splitLayout(cellWidths = 38, 
                                           checkboxInput("replyF05", "f0", value = FALSE, width = NULL))))
          else
              
          if (!emptyF3())
            return(tagList(tL, splitLayout(cellWidths = 38, 
                                             checkboxInput("replyF35", "F3", value = FALSE, width = NULL))))
          else
            return(NULL)
        }
      })

      output$getOpts5 <- renderUI(
      {
        req(input$selMeth5)
 
        if (input$selMeth5=="Evaluate")
        {
          return(radioButtons(inputId  = 'selProc5',
                              label    = 'Procedure:',
                              choices  = c("Adank et al. (2004) LDA",
                                           "Adank et al. (2004) MANOVA"),
                              selected =   "Adank et al. (2004) LDA",
                              inline   = FALSE))
        }
 
        if (input$selMeth5=="Compare")
        {
          return(radioButtons(inputId  = 'selConv5',
                              label    = 'Conversion:',
                              choices  = c("Scaling",
                                           "Normalization"),
                              selected =   "Scaling",
                              inline   = FALSE))
        }
      })

      output$getEval5 <- renderUI(
      {
        if (input$selMeth5=="Evaluate")
        {
          return(radioButtons(inputId  = 'selEval52',
                              label    = 'Method:',
                              choices  = c("preserve phonemic variation",
                                           "minimize anatomic variation",
                                           "preserve sociolinguistic variation"),
                              selected =   "preserve phonemic variation",
                              inline   = FALSE))
        }
      })

      output$goButton <- renderUI(
      {
        if (length(unique(vowelTab()$speaker)) > 1)
        {
          if (length(vowelSame()$vowel) > 0)
            return(div(style="text-align: center;", actionButton('getEval', 'Go!')))
          else
          {
            showNotification("There are no sounds shared by all speakers!", type = "error", duration = 30)
            return(NULL)
          }
        }
        else
        {
          showNotification("You need multiple speakers for this function!", type = "error", duration = 30)
          return(NULL)
        }
      })

      vowelScale5 <- reactive(
      {
        return(vowelScale(vowelSame(), global$replyScale5, 50))
      })

      vowelNorm5 <- reactive(
      {
        if (!is.null(input$replyTimesN5))
          replyTimesN <- input$replyTimesN5
        else
          return(NULL)

        indexDuration <- grep("^duration$", tolower(colnames(vowelScale5())))
        nPoints       <- (ncol(vowelScale5()) - indexDuration) / 5
        
        if (max(replyTimesN) > nPoints)
          replyTimesN <- Round(nPoints/2)
        else {}
        
        vL1 <- vowelLong1(vowelScale5(),replyTimesN)
        vL2 <- vowelLong2(vL1)
        vL3 <- vowelLong3(vL1)
        vL4 <- vowelLong4(vL1)
        vLD <- vowelLongD(vL1)
    
        return(vowelNormF(vowelScale5(), vL1, vL2, vL3, vL4, vLD, global$replyNormal5))
      })

      vowelSubS5 <- reactive(
      {
        if (is.null(vowelNorm5()) || (nrow(vowelNorm5())==0)  || (length(input$replyTimes5)==0))
          return(NULL)

        vT <- vowelNorm5()

        indexVowel <- grep("^vowel$", colnames(vowelTab()))
        nPoints <- (ncol(vowelTab()) - (indexVowel + 1))/5

        if ((nrow(vT)>0) && (max(as.numeric(input$replyTimes5))<=nPoints))
        {
          if ((!is.null(input$replyVars51)) && (length(input$replyVars51) > 0))
          {
            indices <- which(colnames(vT) %in% input$replyVars51)
            vT$vars1 <- unite(data=vT[,1:(indexVowel-1)], col="all", indices, sep = "_", remove = FALSE)$all
          }
          else
            vT$vars1 <- "none"

          if ((!is.null(input$replyVars52)) && (length(input$replyVars52) > 0))
          {
            indices <- which(colnames(vT) %in% input$replyVars52)
            vT$vars2 <- unite(data=vT[,1:(indexVowel-1)], col="all", indices, sep = "_", remove = FALSE)$all
          }
          else
            vT$vars2 <- "none"

          vT0 <- data.frame()

          for (i in (1:length(input$replyTimes5)))
          {
            Code <- strtoi(input$replyTimes5[i])

            indexF0 <- indexVowel + 3 + ((Code-1) * 5)
            indexF1 <- indexVowel + 4 + ((Code-1) * 5)
            indexF2 <- indexVowel + 5 + ((Code-1) * 5)
            indexF3 <- indexVowel + 6 + ((Code-1) * 5)

            if (any(is.na(vT[,indexF0])))
              vT[,indexF0] <- 0
            if (any(is.na(vT[,indexF1])))
              vT[,indexF1] <- 0
            if (any(is.na(vT[,indexF2])))
              vT[,indexF2] <- 0
            if (any(is.na(vT[,indexF3])))
              vT[,indexF3] <- 0

            vT0 <- rbind(vT0, data.frame(vowel   = vT$vowel    ,
                                         speaker = vT$speaker  ,
                                         time    = i           ,
                                         vars1   = vT$vars1    ,
                                         vars2   = vT$vars2    ,
                                         f0      = vT[,indexF0],
                                         F1      = vT[,indexF1],
                                         F2      = vT[,indexF2],
                                         F3      = vT[,indexF3]))
          }

          return(stats::aggregate(cbind(f0,F1,F2,F3)~vowel+speaker+time+vars1+vars2, data=vT0, FUN=mean))
        }
        else
          return(data.frame())
      })

      asPolySet <- function(df, PID)
      {
        df$PID <- PID
        df$POS <- 1:nrow(df)
        return(df)
      }

      perc <- function(yp)
      {
        eql <- nrow(subset(yp, yp[,1]==yp[,2]))
        all <- nrow(yp)

        return((eql/all)*100)
      }

      round2 <- function(x, n=0)
      {
        scale<-10^n
        return(trunc(x*scale+sign(x)*0.5)/scale)
      }

      formatMatrix <- function(matrix0, Scale, Normal)
      {
        matrix0 <- round2(matrix0, n=3)
        matrix0  <- as.data.frame(matrix0)
        matrix0[is.na(matrix0)] <- "-"
        colnames(matrix0) <- Scale[1:(length(Scale)-1)]
        matrix0 <- cbind(c("None", as.character(Normal[2:length(Normal)])), matrix0)
        colnames(matrix0)[1] <- " "
        return(matrix0)
      }
      
      evalResults <- eventReactive(input$getEval,
      {
        req(vowelTab())

        if ((!emptyF0()) && (input$replyF05))
          replyF05 <- T
        else
          replyF05 <- F
        
        if ((!emptyF3()) && (input$replyF35))
          replyF35 <- T
        else
          replyF35 <- F
        
        Scale  <- unlist(optionsScale())
        Normal <- unlist(optionsNormal(vowelTab(), " Hz", !replyF05, !replyF35))

        allScalesAllowed <- c("",
                              " Peterson",
                              " Syrdal & Gopal",
                              " Thomas & Kendall",
                              " Gerstman",
                              " Lobanov",
                              " Watt & Fabricius",
                              " Fabricius et al.",
                              " Bigham",
                              " Heeringa & Van de Velde I" ,
                              " Heeringa & Van de Velde II",
                              " Johnson")

        matrix1  <- matrix(NA, nrow = length(Normal), ncol = (length(Scale)-1))
        matrix2  <- matrix(NA, nrow = length(Normal), ncol = (length(Scale)-1))
        matrix3  <- matrix(NA, nrow = length(Normal), ncol = (length(Scale)-1))
        matrix4  <- matrix(NA, nrow = length(Normal), ncol = (length(Scale)-1))
        matrix5  <- matrix(NA, nrow = length(Normal), ncol = (length(Scale)-1))
        matrix6  <- matrix(NA, nrow = length(Normal), ncol = (length(Scale)-1))
        
         loop <- 0
        nLoop <- (length(Scale)-1) * length(Normal)

        withProgress(value = 0, style = "old",
        {
          for (i in 1:(length(Scale)-1))
          {
            global$replyScale5 <- Scale[i]

            for (j in 1:length(Normal))
            {
              loop <- loop + 1
              incProgress((1/nLoop), message = paste("Calculating ...", format(round2((loop/nLoop)*100)), "%"))

              if ((Scale[i]==" Hz") | is.element(Normal[j], allScalesAllowed))
              {
                global$replyNormal5 <- Normal[j]
                vT <- vowelSubS5(); if (is.null(vT)) next()
                
                if (length(unique(vT$vowel)) > 1)
                {
                  m1 <- 0
                  m2 <- 0
                  times <- sort(unique(vT$time))
                  
                  for (k in 1:length(times))
                  {
                    vTsub <- subset(vT, time==times[k])
                    preds <- c()
                    
                    if (!emptyF0() && input$replyF05 &&
                       (sd(vTsub$f0) > 0.000001))
                      preds <- cbind(preds, vTsub$f0)
                    
                    if (sd(vTsub$F1) > 0.000001) 
                      preds <- cbind(preds, vTsub$F1)
                    
                    if (sd(vTsub$F2) > 0.000001) 
                      preds <- cbind(preds, vTsub$F2)
                    
                    if (!emptyF3() && input$replyF35 &&
                       (sd(vTsub$F3) > 0.000001))
                      preds <- cbind(preds, vTsub$F3)
                    
                    model <- lda(factor(vTsub$vowel)~preds)
                    p <- predict(model)
                    yp <- cbind(as.character(vTsub$vowel), as.character(p$class))
                    m1 <- m1 + perc(yp)

                    model <- vegan::adonis2(preds~factor(vTsub$vowel), permutations = 99, method="euclidean", na.rm=TRUE)
                    m2 <- m2 + (100 * model$R2[1])
                  }
                  
                  matrix1[j,i] <- m1/length(times)
                  matrix2[j,i] <- m2/length(times)
                }

                if (length(unique(vT$vars1)) > 1)
                {
                  m3 <- 0
                  m4 <- 0
                  voweltimes <- unique(data.frame(vowel=vT$vowel, time=vT$time))
                  
                  for (k in 1:nrow(voweltimes))
                  {
                    vTsub <- subset(vT, (vowel==voweltimes$vowel[k]) & (time==voweltimes$time[k]))
                    preds <- c()

                    if (!emptyF0() && input$replyF05 &&
                       (sd(vTsub$f0) > 0.000001))
                      preds <- cbind(preds, vTsub$f0)
                    
                    if (sd(vTsub$F1) > 0.000001) 
                      preds <- cbind(preds, vTsub$F1)
                    
                    if (sd(vTsub$F2) > 0.000001) 
                      preds <- cbind(preds, vTsub$F2)
                    
                    if (!emptyF3() && input$replyF35 &&
                        (sd(vTsub$F3) > 0.000001))
                      preds <- cbind(preds, vTsub$F3)
                    
                    model <- lda(factor(vTsub$vars1)~preds)
                    p <- predict(model)
                    yp <- cbind(as.character(vTsub$vars1), as.character(p$class))
                    m3 <- m3 + perc(yp)
                    
                    model <- vegan::adonis2(preds~factor(vTsub$vars1), permutations = 99, method="euclidean", na.rm=TRUE)
                    m4 <- m4 + (100 * model$R2[1])
                  }
      
                  matrix3[j,i] <- m3/nrow(voweltimes)
                  matrix4[j,i] <- m4/nrow(voweltimes)
                }

                if (length(unique(vT$vars2)) > 1)
                {
                  m5 <- 0
                  m6 <- 0
                  voweltimes <- unique(data.frame(vowel=vT$vowel, time=vT$time))

                  for (k in 1:nrow(voweltimes))
                  {
                    vTsub <- subset(vT, (vowel==voweltimes$vowel[k]) & (time==voweltimes$time[k]))
                    preds <- c()
                    
                    if (!emptyF0() && input$replyF05 &&
                       (sd(vTsub$f0) > 0.000001))
                      preds <- cbind(preds, vTsub$f0)
                    
                    if (sd(vTsub$F1) > 0.000001) 
                      preds <- cbind(preds, vTsub$F1)
                    
                    if (sd(vTsub$F2) > 0.000001) 
                      preds <- cbind(preds, vTsub$F2)
                    
                    if (!emptyF3() && input$replyF35 &&
                        (sd(vTsub$F3) > 0.000001))
                      preds <- cbind(preds, vTsub$F3)
                    
                    model <- lda(factor(vTsub$vars2)~preds)
                    p <- predict(model)
                    yp <- cbind(as.character(vTsub$vars2), as.character(p$class))
                    m5 <- m5 + perc(yp)
                    
                    model <- vegan::adonis2(preds~factor(vTsub$vars2), permutations = 99, method="euclidean", na.rm=TRUE)
                    m6 <- m6 + (100 * model$R2[1])
                  }
                  
                  matrix5[j,i] <- m5/nrow(voweltimes)
                  matrix6[j,i] <- m6/nrow(voweltimes)
                }
              }
            }
          }
        })

        matrix1 <- formatMatrix(matrix1, Scale, Normal)
        matrix2 <- formatMatrix(matrix2, Scale, Normal)
        matrix3 <- formatMatrix(matrix3, Scale, Normal)
        matrix4 <- formatMatrix(matrix4, Scale, Normal)
        matrix5 <- formatMatrix(matrix5, Scale, Normal)
        matrix6 <- formatMatrix(matrix6, Scale, Normal)
        
        return(list(matrix1, matrix2, matrix3, matrix4, matrix5, matrix6))
      })

      showResults1 <- eventReactive(input$getEval,
      {
        req(vowelTab())

        if ((!emptyF0()) && (input$replyF05))
          replyF05 <- T
        else
          replyF05 <- F
        
        if ((!emptyF3()) && (input$replyF35))
          replyF35 <- T
        else
          replyF35 <- F
        
        Scale  <- unlist(optionsScale())
        Normal <- unlist(optionsNormal(vowelTab(), " Hz", !replyF05, !replyF35))

        matrix0  <- matrix(NA, nrow = (length(Scale)-1), ncol = (length(Scale)-1))

        rownames(matrix0) <- Scale[1:(length(Scale)-1)]
        colnames(matrix0) <- Scale[1:(length(Scale)-1)]

         loop <- 0
        nLoop <- ((length(Scale)-1) * ((length(Scale)-1)-1))/2

        global$replyNormal5 <- ""

        withProgress(value = 0, style = "old",
        {
          for (i in 2:(length(Scale)-1))
          {
            global$replyScale5  <- Scale[i]
            vT1 <- vowelSubS5()

            for (j in 1:(i-1))
            {
              loop <- loop + 1
              incProgress((1/nLoop), message = paste("Calculating ...", format(round2((loop/nLoop)*100)), "%"))

              global$replyScale5  <- Scale[j]
              vT2 <- vowelSubS5()

              if (emptyF3() || !input$replyF35)
                Cor <- 1-((stats::cor(vT1$F1, vT2$F1) + stats::cor(vT1$F2, vT2$F2))/2)
              else
                Cor <- 1-((stats::cor(vT1$F1, vT2$F1) + stats::cor(vT1$F2, vT2$F2) + stats::cor(vT1$F3, vT2$F3))/3)

              matrix0[i,j] <- Cor
              matrix0[j,i] <- Cor
            }
          }
        })

        for (i in 1:(length(Scale)-1))
        {
          matrix0[i,i] <- 0
        }

        return(matrix0)
      })

      showResults2 <- eventReactive(input$getEval,
      {
        req(vowelTab())

        if ((!emptyF0()) && (input$replyF05))
          replyF05 <- T
        else
          replyF05 <- F
        
        if ((!emptyF3()) && (input$replyF35))
          replyF35 <- T
        else
          replyF35 <- F
        
        Scale  <- unlist(optionsScale())
        Normal <- unlist(optionsNormal(vowelTab(), " Hz", !replyF05, !replyF35))

        matrix0  <- matrix(NA, nrow = length(Normal), ncol = length(Normal))

        rownames(matrix0) <- c(" None", Normal[2:length(Normal)])
        colnames(matrix0) <- c(" None", Normal[2:length(Normal)])

        global$replyScale5 <- " Hz"

         loop <- 0
        nLoop <- (length(Normal) * (length(Normal)-1))/2

        withProgress(value = 0, style = "old",
        {
          for (i in 2:length(Normal))
          {
            global$replyNormal5 <- Normal[i]
            vT1 <- vowelSubS5()

            for (j in 1:(i-1))
            {
              loop <- loop + 1
              incProgress((1/nLoop), message = paste("Calculating ...", format(round2((loop/nLoop)*100)), "%"))

              global$replyNormal5 <- Normal[j]
              vT2 <- vowelSubS5()

              if (emptyF3() || !input$replyF35)
                Cor <- 1-((stats::cor(vT1$F1, vT2$F1) + stats::cor(vT1$F2, vT2$F2))/2)
              else
                Cor <- 1-((stats::cor(vT1$F1, vT2$F1) + stats::cor(vT1$F2, vT2$F2) + stats::cor(vT1$F3, vT2$F3))/3)

              matrix0[i,j] <- Cor
              matrix0[j,i] <- Cor
            }
          }
        })

        for (i in 1:length(Normal))
        {
          matrix0[i,i] <- 0
        }

        return(matrix0)
      })

      output$table5 <- renderFormattable(
      {
        if (length(unique(vowelTab()$speaker)) < 2)
          return(NULL)
  
        req(evalResults())
            
        df <- data.frame()
  
        if ((input$selProc5=="Adank et al. (2004) LDA")    && (input$selEval52 == "preserve phonemic variation"))
        {
          df <- evalResults()[[1]]
          col1 <- "turquoise"
          col2 <- "yellow"
        }
  
        if ((input$selProc5=="Adank et al. (2004) LDA")    && (input$selEval52 == "minimize anatomic variation"))
        {
          df <- evalResults()[[3]]
          col1 <- "yellow"
          col2 <- "turquoise"
        }
  
        if ((input$selProc5=="Adank et al. (2004) LDA")    && (input$selEval52 == "preserve sociolinguistic variation"))
        {
          df <- evalResults()[[5]]
          col1 <- "turquoise"
          col2 <- "yellow"
        }
  
        if ((input$selProc5=="Adank et al. (2004) MANOVA") && (input$selEval52 == "preserve phonemic variation"))
        {
          df <- evalResults()[[2]]
          col1 <- "turquoise"
          col2 <- "yellow"
        }
        
        if ((input$selProc5=="Adank et al. (2004) MANOVA") && (input$selEval52 == "minimize anatomic variation"))
        {
          df <- evalResults()[[4]]
          col1 <- "yellow"
          col2 <- "turquoise"
        }
        
        if ((input$selProc5=="Adank et al. (2004) MANOVA") && (input$selEval52 == "preserve sociolinguistic variation"))
        {
          df <- evalResults()[[6]]
          col1 <- "turquoise"
          col2 <- "yellow"
        }
        
        formattable(df, align = rep("l", 11), list(formattable::area() ~ color_tile(col1, col2)))
      })

      output$graph5 <- renderPlot(
      {
        req(input$selConv5)

        if (input$selConv5=="Scaling")
          clus <- stats::hclust(stats::as.dist(showResults1()), method="average")

        if (input$selConv5=="Normalization")
          clus <- stats::hclust(stats::as.dist(showResults2()), method="average")

        dendro <- dendro_data(stats::as.dendrogram(clus), type = "rectangle")
        dendro$labels$label <- paste0("  ", dendro$labels$label)

        rownames(dendro$labels) <- 1:nrow(dendro$labels)

        gp <- ggplot(dendro$segments) +
              geom_segment(aes(x = x, y = y, xend = xend, yend = yend)) +
              geom_text (data = dendro$labels, aes(x, y, label = label), hjust = 0, angle = 0, size = 4, family="Helvetica") +
              scale_y_reverse(expand = c(0.5, 0)) +
              coord_flip() +
              ggtitle('') +
              xlab(NULL) + ylab(NULL) +
              theme_bw() +
              theme(text            =element_text(size=22, family="Helvetica"),
                    plot.title      =element_text(face="bold", hjust = 0.5),
                    axis.text       =element_blank(),
                    axis.ticks      =element_blank(),
                    panel.grid.major=element_blank(),
                    panel.grid.minor=element_blank()) +
             guides(color = guide_legend(override.aes = list(linetype = 0, shape=3)))

        print(gp)
      })

      output$Graph5 <- renderUI(
      {
        if (input$selMeth5=="Evaluate")
        {
          fluidPage(
            style = "padding:0; margin:0; font-size: 59%; font-family: Ubuntu; min-height: 500px;",
            formattableOutput("table5", height=0)
          )
        }
        else
        {
          fluidPage(
            style = "padding:0; margin:0;",
            plotOutput("graph5", height="500px")
          )
        }
      })
    }
  )
}

################################################################################

long2wide <- function(vT, isLong)
{
  if (isLong)
  {
    timepoint <- vT$timepoint
    vT$timepoint <- NULL
    
    indexDuration <- grep("^duration$", tolower(colnames(vT)))
    ngroups <- (ncol(vT) - indexDuration) / 5
    
    if (ngroups == 1)
    {
      freq <- data.frame(table(timepoint))
      
      if (mean(freq$Freq) != round(mean(freq$Freq)))
      {
        message("The number of time points is not the same for all cases!")
        return(invisible(NULL))
      }
      
      if (!unique((sort(freq$timepoint) == 1:length(freq$timepoint))))
      {
        message("Numbering of time points is not correct!")
        return(invisible(NULL))
      }
      
      superIndex <- ""
      
      for (i in 1:(indexDuration-1))
      {
        superIndex <- paste0(superIndex, vT[,i])
      }
      
      ntimepoints <- nrow(freq)
      
      if ((nrow(vT)/ntimepoints) > length(unique(superIndex)))
      {
        message("Not all cases are uniquely defined!")
        return(invisible(NULL))
      }
      
      vT <- vT[order(superIndex, timepoint),]
      
      df <- dplyr::filter(vT, ((dplyr::row_number() %% ntimepoints)) == 1)
      df <- df[, 1:indexDuration]
      
      for (g in (1:ntimepoints))
      {
        if (g==ntimepoints) g0 <- 0 else g0 <- g
        
        df0 <- dplyr::filter(vT, ((dplyr::row_number() %% ntimepoints)) == g0)
        df0 <- df0[, (indexDuration+1):(indexDuration+5)]
        
        df <- cbind(df, df0)
      }
      
      return(df)
    }
    else
    {
      message("Cannot have both a variable timepoint and multiple time points in one row!")
      return(invisible(NULL))
    }
  }
  else
    return(vT)
}

wide2long <- function(vT, isLong)
{
  if (isLong)
  {
    indexDuration <- grep("^duration$", tolower(colnames(vT)))
    ngroups       <- (ncol(vT) - indexDuration) / 5
    
    vT$index <- 1:nrow(vT)
    
    df <- data.frame()
    
    for (g in (1:ngroups))
    {
      df1 <- vT[,1:2]
      df1$timepoint <- g
      df1$index <- vT$index
      df2 <- vT[,3:indexDuration]
      
      first <- indexDuration + ((g-1) * 5) + 1
      last  <- first + 4
      
      df3 <- vT[,first:last]
      
      df <- rbind(df, cbind(df1,df2,df3))
    }
    
    df <- df[order(df$index, df$timepoint),]
    df$index <- NULL
    
    return(df)
  }
  else
    return(vT)
}

#' @name
#' normalizeFormants
#'
#' @title
#' Normalize vowel formants.
#' 
#' @aliases
#' normalizeFormants
#'
#' @description
#' Scale and/or normalize formants F1, F2 and F3.
#'
#' @usage
#' normalizeFormants(vowelTab, replyScale, replyNormal, replyTimesN)
#' 
#' @param 
#' vowelTab A data frame containing acoustic vowel measurements; the format is described at https://www.visiblevowels.org/#help .
#' @param
#' replyScale Choose from: "Hz", "bark I", "bark II", "bark III", "ERB I", "ERB II", "ERB III", "ln", "mel I", "mel II", "ST".
#' @param
#' replyNormal Choose from: "none", "Peterson", "Sussman", "Syrdal & Gopal", "Miller", "Thomas & Kendall", "Gerstman", "Lobanov", "Watt & Fabricius", "Fabricius et al.", "Bigham", "Heeringa & Van de Velde I", "Heeringa & Van de Velde II", "Nearey I", "Nearey II", "Barreda & Nearey I", "Barreda & Nearey II", "Labov log-mean I", "Labov log-geomean I", "Labov log-mean II", "Labov log-geomean II", "Johnson".
#' @param
#' replyTimesN If measurements are provided for multiple time points per vowel, provide the indices of the time points that should be included when descriptives such as minimum, maximum, mean and standard deviation are calculated by some normalization methods; when there is just one time point, give index 1; a set of multiple indices are given as a vector, for example, when there are three indices and you want the first and third index be used, give c(1,3).
#' 
#' @export
#' normalizeFormants
NULL

normalizeFormants <- function(vowelTab = data.frame(), replyScale = "Hz", replyNormal = "none", replyTimesN = c())
{
  if (nrow(vowelTab) == 0)
  {
    message("Error: Input table is missing")
    return(invisible(NULL))
  }
  
  if (length(replyTimesN) == 0)
  {
    message("Error: Vector of indices of descriptive time points are missing")
    return(invisible(NULL))
  }

  isLong <- ("timepoint" %in% colnames(vowelTab))
  vowelTab <- long2wide(vowelTab,isLong)
  indexDuration <- grep("^duration$", tolower(colnames(vowelTab)))
  
  if (indexDuration > 3)
  {
    cnames <- colnames(vowelTab)
    vowelTab <- data.frame(vowelTab[,1], vowelTab[,3:(indexDuration-1)], vowelTab[,2], vowelTab[,indexDuration:ncol(vowelTab)])
    cnames <- c(cnames[1],cnames[3:(indexDuration-1)],cnames[2],cnames[indexDuration:ncol(vowelTab)])
    colnames(vowelTab) <- cnames
  }
  else {}
  
  if ((replyScale=="none") | (replyScale==""))
    replyScale <- " Hz"
  else
    replyScale <- paste0(" ", replyScale)

  vowelScale <- vowelScale(vowelTab, replyScale, 0)
  
  vL1 <- vowelLong1(vowelScale,replyTimesN)
  vL2 <- vowelLong2(vL1)
  vL3 <- vowelLong3(vL1)
  vL4 <- vowelLong4(vL1)
  vLD <- vowelLongD(vL1)
  
  if ((replyNormal=="none") | (replyNormal==""))
    replyNormal <- ""
  else
    
  if  (replyNormal=="Labov log-mean I")
    replyNormal <- " Labov 1"
  else
    
  if  (replyNormal=="Labov log-geomean I")
    replyNormal <- " LABOV 1"
  else
    
  if  (replyNormal=="Labov log-mean II")
    replyNormal <- " Labov 2"
  else
    
  if  (replyNormal=="Labov log-geomean II")
    replyNormal <- " LABOV 2"
  else
    replyNormal <- paste0(" ", replyNormal)
  
  vowelTab <- vowelNormF(vowelScale, vL1, vL2, vL3, vL4, vLD, replyNormal)
  
  if (indexDuration > 3)
  {
    cnames <- colnames(vowelTab)
    vowelTab <- data.frame(vowelTab[,1], vowelTab[,indexDuration-1], vowelTab[,2:(indexDuration-2)], vowelTab[,indexDuration:ncol(vowelTab)])
    cnames <- c(cnames[1], cnames[indexDuration-1], cnames[2:(indexDuration-2)], cnames[indexDuration:ncol(vowelTab)])
    colnames(vowelTab) <- cnames
  }
  else {}  
  
  vowelTab <- wide2long(vowelTab, isLong)

  return(vowelTab)
}

#' @name
#' normalizeDuration
#'
#' @title
#' Normalize duration
#' 
#' @aliases 
#' normalizeDuration
#'
#' @description
#' Normalize duration of vowels.
#'
#' @usage
#' normalizeDuration(vowelTab, replyNormal)
#' 
#' @param 
#' vowelTab A data frame containing acoustic vowel measurements; the format is described at https://www.visiblevowels.org/#help .
#' @param
#' replyNormal Choose from: "none", "Lobanov".
#' 
#' @export
#' normalizeDuration
NULL

normalizeDuration <- function(vowelTab = data.frame(), replyNormal = "")
{
  if (nrow(vowelTab) == 0)
  {
    message("Error: Input table is missing")
    return(invisible(NULL))
  }
  
  isLong <- ("timepoint" %in% colnames(vowelTab))
  vowelTab <- long2wide(vowelTab,isLong)
  indexDuration <- grep("^duration$", tolower(colnames(vowelTab)))
  
  if (indexDuration > 3)
  {
    cnames <- colnames(vowelTab)
    vowelTab <- data.frame(vowelTab[,1], vowelTab[,3:(indexDuration-1)], vowelTab[,2], vowelTab[,indexDuration:ncol(vowelTab)])
    cnames <- c(cnames[1],cnames[3:(indexDuration-1)],cnames[2],cnames[indexDuration:ncol(vowelTab)])
    colnames(vowelTab) <- cnames
  }
  else {}  
  
  if ((replyNormal=="none") | (replyNormal==""))
    replyNormal <- ""
  else
    replyNormal <- paste0(" ", replyNormal)
  
  vowelTab <- vowelNormD(vowelTab,replyNormal)
  
  if (indexDuration > 3)
  {
    cnames <- colnames(vowelTab)
    vowelTab <- data.frame(vowelTab[,1], vowelTab[,indexDuration-1], vowelTab[,2:(indexDuration-2)], vowelTab[,indexDuration:ncol(vowelTab)])
    cnames <- c(cnames[1], cnames[indexDuration-1], cnames[2:(indexDuration-2)], cnames[indexDuration:ncol(vowelTab)])
    colnames(vowelTab) <- cnames
  }
  else {}

  vowelTab <- wide2long(vowelTab, isLong)
  
  return(vowelTab)
}

################################################################################

Try the visvow package in your browser

Any scripts or data that you put into this service are public.

visvow documentation built on Oct. 16, 2023, 5:06 p.m.