Nothing
#' Sound waves onto morphometric data
#'
#' @description \code{eigensound} is the main feature of \code{SoundShape} package. For each \code{".wav"} file on a given folder, the fuction will compute spectrogram data and acquire semilandmarks using a 3D representation of sound (\code{analysis.type = "threeDshape"}), allowing its users to acquire, and simultaneously store point coordinates (i.e. semilandmarks) as an R object, and/or in TPS format – the native file format of James Rohlf’s TPS series (Rohlf, 2015).
#'
#' Moreover, \code{eigensound} also allow its user to export 2D and 3D spectrogram images (\code{plot.exp = TRUE}) that are helpful during the protocol for error verification and for illustrative purposes (see Rocha & Romano \emph{in prep}). Alternativaly, \code{eigensound} feature the option of acquiring semilandmarks as the cross-correlation between energy quantiles and a curve of relative amplitude from 2D spectrograms (\code{analysis.type = "twoDshape"}; see \code{Details} section).
#'
#' @param analysis.type type of analysis intended. If \code{analysis.type = "threeDshape"}, semilandmarks are acquired from spectrogram data using a 3D representation of sound (same as in MacLeod et al., 2013). If \code{analysis.type = "twoDshape"} and \code{add.points = TRUE}, semilandmarks are acquired using energy quantiles and a 2D curve of relative amplitude. By default: \code{analysis.type = NULL} (i.e. method must be specified before the analysis).
#' @param wav.at filepath to the folder where \code{".wav"} files are stored. Should be presented between quotation marks. By default: \code{wav.at = NULL} (i.e. user must specify the filepath to \code{".wav"} files)
#' @param store.at filepath to the folder where spectrogram plots and \code{tps} file will be stored. Should be presented between quotation marks. By default: \code{store.at = wav.at} (i.e. store outputs in the same folder as \code{".wav"} files)
#' @param dBlevel absolute amplitude value to be used as relative amplitude contour, which will serve as reference for semilandmark acquisition in both \code{analysis.type = "threeDshape"} and \code{"twoDshape"}. By default: \code{dBlevel = 25}
#' @param flim modifications of the frequency limits (Y-axis). Vector with two values in kHz. By default: \code{flim = c(0, 10)}
#' @param tlim modifications of the time limits (X-axis). Vector with two values in seconds. By default: \code{tlim = c(0, 1)}
#' @param x.length only applies when \code{analysis.type = "threeDshape"}. Length of sequence (i.e. number of cells per side on sound window) to be used as sampling grid coordinates on the time (X-axis). By default: \code{x.length = 80}
#' @param y.length only applies when \code{analysis.type = "threeDshape"}. Length of sequence (i.e. number of cells per side on sound window) to be used as sampling grid coordinates on the frequency (Y-axis). By default: \code{y.length = 60}
#' @param log.scale only applies when \code{analysis.type = "threeDshape"}. A logical. If \code{TRUE}, \code{eigensound} will use a logarithmic scale on the time (X-axis), which is recommeded when the analyzed sounds present great variation on this axis (e.g. emphasize short duration sounds). If \code{FALSE}, a linear scale is used instead (same as MacLeod et al., 2013). By default: \code{log.scale = TRUE}
#' @param back.amp only applies when \code{analysis.type = "twoDshape"} and \code{plot.exp = TRUE}. Absolute amplitude value to be used as background in the 2D spectrogram plot. By default: \code{back.amp = 35}
#' @param add.points only applies when \code{analysis.type = "twoDshape"}. A logical. If \code{TRUE}, \code{eigensound} will compute semilandmarks acquired by cross-correlation between energy quantiles (i.e. \code{EQ}) and a curve of relative amplitude (i.e. \code{dBlevel}). If \code{plot.exp = TRUE}, semilandmarks will be included in spectrogram plots. By default: \code{add.points = FALSE} (see \code{Details})
#' @param add.contour only applies when \code{analysis.type = "twoDshape"} and \code{plot.exp = TRUE}. A logical. If \code{TRUE}, exported spectrogram plots will include the curves of relative amplitude at the level specified by \code{dBlevel}. By default: \code{add.contour = TRUE}
#' @param lwd only applies when \code{plot.exp = TRUE} and \code{add.contour = TRUE}. The line width for the curves of relative amplitude at the level specified by \code{dBlevel}. Same as in \code{\link{par}}. By default: \code{lwd = 1}
#' @param EQ only applies when \code{analysis.type = "twoDshape"} and \code{add.points = TRUE}. A vector of energy quantiles intended (with \code{0 < EQ < 1}). By default: \code{EQ = c(0.05, 0.15, 0.3, 0.5, 0.7, 0.85, 0.95)} \strong{Note:} When dealing with narrow banded calls, consider reducing the number of quantiles to prevent errors in the analysis.
#' @param mag.time only applies when \code{analysis.type = "twoDshape"}. Optional argument for magnifying the time coordinates (X-axis). This is sometimes desired for small sound windows (e.g. less than 1 s), in which the time coordinates will be on a different scale than that of frequency coordinates. In those cases, it is recommended to include \code{mag.time = 10} or \code{mag.time = 100}, depending on the lenght of sound window. By default: \code{mag.time = 1} (i.e. no magnification is performed)
#' @param f sampling frequency of \code{Wave}'s for the analysis (in Hz). By default: \code{f = 44100}
#' @param wl length of the window for the analysis. By default: \code{wl = 512}
#' @param ovlp overlap between two successive windows (in %) for increased spectrogram resolution. By default: \code{ovlp = 70}
#' @param plot.exp a logical. If \code{TRUE}, for each \code{".wav"} file on the folder indicated by \code{wav.at}, \code{eigensound} will store a spectrogram image on the folder indicated by \code{store.at}. Depending on the \code{analysis.type}, plots may consist of 2D or 3D spectrogram images. By default: \code{plot.exp = TRUE}
#' @param plot.as only applies when \code{plot.exp = TRUE}. \code{plot.as = "jpeg"} will generate compressed images for quick inspection of semilandmarks; \code{plot.as = "tiff"} or \code{"tif"} will generate uncompressed high resolution images that can be edited and used for publication. By default: \code{plot.as = "jpeg"}
#' @param plot.type only applies when \code{analysis.type = "threeDshape"} and \code{plot.exp = TRUE}. \code{plot.type = "surface"} will produce simplified 3D sound surfaces from the calculated semilandmarks (same output employed by MacLeod et al., 2013); \code{plot.type = "points"} will produce 3D graphs with semilandmarks as points. By default: \code{plot.type = "surface"}
#' @param rotate.Xaxis only applies when \code{analysis.type = "threeDshape"} and \code{plot.exp = TRUE}. Rotation of the X-axis. Same as \code{theta} from \code{\link[plot3D]{persp3D}}. By default: \code{rotate.Xaxis = 60}
#' @param rotate.Yaxis only applies when \code{analysis.type = "threeDshape"} and \code{plot.exp = TRUE}. Rotation of the Y-axis. Same as \code{phi} from \code{\link[plot3D]{persp3D}}. By default: \code{rotate.Yaxis = 40}
#' @param TPS.file Desired name for the \code{tps} file containing semilandmark coordinates. Should be presented between quotation marks. \strong{Note:} Whenever \code{analysis.type = "twoDshape"}, it will only work if \code{add.points = TRUE}. By default: \code{TPS.file = NULL} (i.e. prevents \code{eigensound} from creating a \code{tps} file)
#'
#' @details
#' When \code{analysis.type = "twoDshape"} and \code{add.points = TRUE}, \code{eigensound} will compute semilandmarks acquired by cross-correlation between energy quantiles (i.e. \code{EQ}) and a curve of relative amplitude (i.e. \code{dBlevel}). However, this is often subtle and prone to incur in errors (e.g. bad alignment of acoustic units; inappropriate X and Y coordinates for the sound window; narrow banded calls). Therefore, a more robust protocol of error verification is achieved using \code{add.points = FALSE} and \code{add.contour = TRUE} (default), which allow for quick verification of acoustic units alignment and the shape of each curve of relative amplitude (specified by \code{dBlevel}).
#'
#' @note
#' In order to store the results from \code{eigensound} function and proceed with the Geometric Morphometric steps of the analysis (e.g. \code{\link[geomorph]{geomorph}} package; Adams et al., 2017), one can simultaneosly assign the function's output to an \code{R} object and/or store them as a \code{tps} file to be used by numerous softwares of geometric analysis of shape, such as the TPS series (Rohlf, 2015).
#'
#' Additionally, one may also export 2D or 3D plots as \code{jpeg} (compressed image) or \code{tiff} (uncompressed image) file formats, which can be edited for publication purposes.
#'
#'
#' @author
#' Pedro Rocha
#'
#' @references
#' Adams, D. C., M. L. Collyer, A. Kaliontzopoulou & Sherratt, E. (2017) \emph{Geomorph: Software for geometric morphometric analyses}. R package version 3.0.5. https://cran.r-project.org/package=geomorph.
#'
#' MacLeod, N., Krieger, J. & Jones, K. E. (2013). Geometric morphometric approaches to acoustic signal analysis in mammalian biology. \emph{Hystrix, the Italian Journal of Mammalogy, 24}(1), 110-125.
#'
#' Rocha, P. & Romano, P. (2021) The shape of sound: A new \code{R} package that crosses the bridge between Bioacoustics and Geometric Morphometrics. \emph{Methods in Ecology and Evolution, 12}(6), 1115-1121.
#'
#' Rohlf, F.J. (2015) The tps series of software. \emph{Hystrix 26}, 9-12.
#'
#' @seealso
#' \code{\link{align.wave}}, \code{\link[geomorph]{geomorph}}, \code{\link[seewave]{seewave}}
#'
#' Useful links:
#' \itemize{
#' \item{\url{https://github.com/p-rocha/SoundShape}}
#' \item{Report bugs at \url{https://github.com/p-rocha/SoundShape/issues}}}
#'
#' @examples
#' library(seewave)
#' library(tuneR)
#'
#' # Create temporary folder to store ".wav" files
#' wav.at <- file.path(base::tempdir(), "eigensound")
#' if(!dir.exists(wav.at)) dir.create(wav.at)
#'
#' # Create temporary folder to store results
#' store.at <- file.path(base::tempdir(), "eigensound-output")
#' if(!dir.exists(store.at)) dir.create(store.at)
#'
#' # Cut acoustic units from original Wave
#' cut.cuvieri <- cutw(cuvieri, f=44100, from=0, to=0.9, output = "Wave")
#' cut.centralis <- cutw(centralis, f=44100, from=0, to=0.9, output = "Wave")
#' cut.kroyeri <- cutw(kroyeri, f=44100, from=0.2, to=1.1, output = "Wave")
#'
#' # Export ".wav" files containing acoustic units and store on previosly created folder
#' writeWave(cut.cuvieri, filename = file.path(wav.at, "cut.cuvieri.wav"), extensible = FALSE)
#' writeWave(cut.centralis, filename = file.path(wav.at, "cut.centralis.wav"), extensible = FALSE)
#' writeWave(cut.kroyeri, filename = file.path(wav.at, "cut.kroyeri.wav"), extensible = FALSE)
#'
#'
#' \donttest{
#' # Create 2D spectrograms using analysis.type = "twoDshape"
#' eigensound(analysis.type = "twoDshape", flim=c(0, 4), tlim=c(0, 0.8),
#' plot.exp=TRUE, wav.at = wav.at, store.at = store.at)
#'
#' # Create 3D spectrograms using analysis.type = "threeDshape" and store point coordinates
#' eig.data <- eigensound(analysis.type = "threeDshape", plot.exp=TRUE,
#' wav.at = wav.at, store.at = store.at, flim=c(0, 4), tlim=c(0, 0.8))
#' }
#'
#' @export
#'
eigensound <- function(analysis.type = NULL, wav.at = NULL, store.at = wav.at, dBlevel=25, flim=c(0, 10), tlim = c(0, 1), x.length=80, y.length=60, log.scale=TRUE, back.amp=35, add.points=FALSE, add.contour=TRUE, lwd=1, EQ= c(0.05, 0.15, 0.3, 0.5, 0.7, 0.85, 0.95), mag.time=1, f=44100, wl=512, ovlp=70, plot.exp = TRUE, plot.as = "jpeg", plot.type = "surface", rotate.Xaxis=60, rotate.Yaxis=40, TPS.file = NULL){
# Invalid arguments
if(is.null(wav.at)) {stop("Use 'wav.at' to specify folder path where '.wav' files are stored")}
if(is.null(analysis.type)) {stop("Method undefined in 'analysis.type'")}
if(!is.null(analysis.type)){
if(analysis.type != "threeDshape" && analysis.type != "twoDshape")
stop("Invalid analysis specified by 'analysis.type'. Choose between 'threeDshape' or 'twoDshape'") } # invalid analysis.type
if(plot.exp == TRUE){
if(plot.as != "jpeg" && plot.as != "tif" && plot.as != "tiff")
stop("Invalid image format specified by 'plot.as'. Choose between 'jpeg', 'tif' or 'tiff'") } # invalid plot.as
if(plot.exp == TRUE && analysis.type == "threeDshape"){
if(plot.type != "surface" && plot.type != "points")
stop("Invalid 3D sound shape specified by 'plot.type'. Choose between 'surface' or 'points'") } # invalid plot.type
if(analysis.type == "twoDshape"){
if(!is.null(TPS.file) && add.points==FALSE)
stop("Must set add.points = TRUE to acquire 2D semilandmarks and store them on a '.tps' file")}
# Create TPS file before analysis
if(!is.null(TPS.file)){
TPS.path <- paste(store.at, "/", TPS.file, ".tps", sep="")
file.create(TPS.path, showWarnings = TRUE)
} # create TPS file
# threeDshape
if(analysis.type == "threeDshape"){
# Acquire 3D semilandmark coordinates for each ".wav" file on a given folder
for(file in list.files(wav.at, pattern = ".wav"))
{
threeD <- tuneR::readWave(paste(wav.at,"/", file, sep=""))
# Store spectrogram as R object
e <- seewave::spectro(threeD, f=f, wl=wl, ovlp=ovlp, flim=flim, tlim=tlim, plot=F)
# Create sequences to use as new grid
freq.seq <- seq(1, length(e$freq), length = y.length)
ifelse(isTRUE(log.scale),
time.seq <- 10^(seq(log10(1), log10(length(e$time)), length.out = x.length)),#log
time.seq <- seq(1, length(e$time), length.out = x.length)) # linear scale
# Subset original coordinates by new sequences
time.sub <- e$time[time.seq]
freq.sub <- e$freq[freq.seq]
# Subset matrix of amplitude values using new sequences
amp.sub <- e$amp[freq.seq, time.seq]
# Reassign background amplitude values
for(i in 1:length(amp.sub)){if(amp.sub[i] == -Inf |amp.sub[i] <= -dBlevel)
{amp.sub[i] <- -dBlevel}}
# Assign time and frequency coordinates as column and row names of amplitude matrix
colnames(amp.sub) <- time.sub
rownames(amp.sub) <- freq.sub
# Transform amplitude matrix into semilandmark 3D coordinates
ind.3D <- as.matrix(stats::setNames(reshape2::melt(t(amp.sub)), c('time', 'freq', 'amp')))
# Store semilandmark coordinates in array
ifelse(!exists('coord3D'),
coord3D <- array(data=ind.3D, dim=c(dim(ind.3D), 1)),
coord3D <- abind::abind(coord3D, ind.3D, along=3))
# Plot semilandmarks as points or sound surface
if(plot.exp==TRUE){
if(plot.as == "jpeg"){grDevices::jpeg(width =5000,height = 3500, units = "px", res = 500,
filename=paste(store.at,"/", sub(".wav","",file), ".jpg", sep=""))} # compressed images
if(plot.as=="tiff"|plot.as=="tif"){grDevices::tiff(width=5000, height=3500, units="px", res=500,
filename=paste(store.at, "/", sub(".wav", "", file), ".tif", sep=""))} # uncompressed images
if(plot.type=="surface"){plot3D::persp3D(x=time.sub, y=freq.sub, z=t(amp.sub),
border="black", lwd=0.1, theta=rotate.Xaxis, phi=rotate.Yaxis, resfac=1, r=3,expand=0.5, cex.axis=0.7,
scale=T, axes=T, col=seewave::spectro.colors(n=100), ticktype="detailed", nticks=4, xlab="Time (s)", ylab="Frequency (kHz)", zlab="Amplitude (dB)",
main= sub(".wav", "", file), clab=expression('Amplitude dB'))}
if(plot.type=="points"){plot3D::scatter3D(x=ind.3D[,1], y=ind.3D[,2], z=ind.3D[,3],
pch=21, cex=0.5, theta=rotate.Xaxis, phi=rotate.Yaxis, resfac=1, r=3, expand=0.5, cex.axis=0.7,
scale=T, axes=T, col=seewave::spectro.colors(n=100), ticktype="detailed", nticks=4,
xlab="Time (s)", ylab="Frequency (kHz)", zlab="Amplitude (dB)",
main= sub(".wav", "", file), clab=expression('Amplitude dB'))}
grDevices::dev.off() } # end threeDshape plot of each ".wav" file
# Export semilandmark coordinates as TPS file
if(!is.null(TPS.file)){
lmline <- paste("LM=", dim(ind.3D)[1], sep="")
write(lmline, TPS.path, append = TRUE)
utils::write.table(ind.3D, TPS.path, col.names = FALSE, row.names = FALSE, append =TRUE)
idline <- paste("ID=", sub(".wav", "", file), sep="")
write(idline, TPS.path, append = TRUE)
write("", TPS.path, append = TRUE)
rm(lmline, idline)
} #end tps file
} #end threeDshape loop
# Assign names to arrays' third dimension
dimnames(coord3D)[[3]] <- sub(".wav","", list.files(wav.at, pattern = ".wav"))
} # End threeDshape
# twoDshape
if(analysis.type == "twoDshape"){
FC <- function(x) {
sign <- ifelse(x >= 0, 1, -1)
res <- abs(diff(sign))
return(res) } # FC function
frequencies <- function (spec, EQ=NULL, f=NULL,mel=FALSE, flim=NULL)
{
fhz <- f
if (!is.null(f) & mel) {f <- 2 * mel(f/2)}
if (is.null(f)) {
if (is.vector(spec))
stop("'f' is missing")
else if (is.matrix(spec))
f <- spec[nrow(spec), 1] * 2000 * nrow(spec)/(nrow(spec) - 1) }
if (is.matrix(spec)) {
freq <- spec[, 1]
freq = freq * 1000
spec <- spec[, 2] }
L <- length(spec)
wl <- L * 2
if (any(spec < 0))
stop("The frequency spectrum to be analysed should not be in dB")
if (!is.null(flim)) {
if (flim[1] < 0 || flim[2] > fhz/2)
stop("'flim' should range between 0 and f/2")
if (mel)
flim <- mel(flim * 1000)/1000 }
else {flim <- c(0, f/2000)}
spec <- spec[(flim[1] * 1000 * wl/f):(flim[2] * 1000 * wl/f)]
amp <- spec/sum(spec)
cumamp <- cumsum(amp)
freq <- seq(from = flim[1] * 1000, to = flim[2] * 1000, length.out = length(spec))
for(i in EQ) # Frequency values according to desired energy quantiles
{ if(!exists("Qlist")) {Qlist <- data.frame()}
if(exists("Qlist")) {
Qtemp <- data.frame("Quantiles" = paste("EQ-", i, sep=""),
"Frequency values" = freq[length(cumamp[cumamp <= i]) + 1])
Qlist <- rbind(Qlist, Qtemp)
rm(Qtemp) } }
results <- Qlist } # frequencies function
# Compute data for each file on a given folder
for(file in list.files(wav.at, pattern = ".wav")) {
Wav <- tuneR::readWave(paste(wav.at,"/", file, sep=""))
if(add.points==TRUE){
# Store spectrogram as R object
s <- seewave::spectro(Wav,f=f,wl=wl,ovlp=ovlp,flim=flim,tlim=tlim,plot=F)
# Selection of longest relative amplitude contour
con <- grDevices::contourLines(x=s$time, y=s$freq, z=t(s$amp),
levels=seq(-dBlevel, -dBlevel, 1))
n <- numeric(length(con))
for(i in 1:length(con)) n[i] <- length(con[[i]]$x)
n.max <- which.max(n)
con <- con[[n.max]]
t.beg <- min(con$x)
t.end <- max(con$x)
f.min <- min(con$y) # Minimum frequency
t.min <- con$x[con$y==f.min] # Time value associated
f.max <- max(con$y) # Maximum frequency
t.max <- con$x[con$y==f.max] # Time value associated
# Power spectrum calculated within maximum and minimum frequency values
pwr.spec <- seewave::spec(Wav, f=f, wl=wl, from=t.beg, to=t.end, plot=F)
fvalues <- frequencies(pwr.spec, EQ=EQ, f=f, flim=c(f.min, f.max))
fvalues$Frequency.values <- (fvalues$Frequency.values)/1000 # Convert to kHz
# Crossing between frequency quantiles and relative amplitude contour
for(i in seq_along(fvalues$Frequency.values)) {
fc.res <- FC(con$y - fvalues$Frequency.values[i])
t.pos <- which(fc.res==2)
if(!exists("t1")|!exists("t2")){
t1 <- vector(mode="integer", length=length(fvalues$Frequency.values))
t2 <- vector(mode="integer", length=length(fvalues$Frequency.values)) }
if(exists("t1")|exists("t2")){
t1[i] <- min(con$x[t.pos])
t2[i] <- max(con$x[t.pos]) } } # end crossing EQ
# Coordinates for plotting
SM1 <- data.frame(cbind(fvalues, "Time"= t1))
SM2 <- data.frame(cbind(fvalues, "Time"= t2))
# Re-order SM1 so that points will be anticlockwise
SM1 <- SM1[order(SM1[,2], decreasing=T), ]
# Store SM-min and SM-max in a data frame
SM.min <- data.frame("Quantiles" = "EQ-min",
"Frequency.values"= f.min, "Time"= t.min)
SM.max <- data.frame("Quantiles" = "EQ-max",
"Frequency.values"= f.max, "Time"= t.max)
# Semilandmark numbers
SM.numbers <- as.factor(1:((length(EQ)*2) + 2)) # Twice the length of EQ plus the SM-min and SM-max
# Complete data.frame with coordinates of x and y
SM <- cbind(SM.numbers, data.frame(rbind(SM.max, SM1, SM.min, SM2)))
# Magnify time coordinates
SM[,4] <- SM[,4]*mag.time
# Store semilandmark coordinates as array of matrices
SM.mtx <- as.matrix(SM[,4:3])
ifelse(!exists('coord2D'),
coord2D <- array(data=SM.mtx, dim=c(dim(SM.mtx), 1)),
coord2D <- abind::abind(coord2D, SM.mtx, along=3))
} # end add.points = TRUE
# Plot spectrogram
if(plot.exp == TRUE){
if(plot.as == "jpeg"){grDevices::jpeg(width =5000,height = 3500, units = "px", res = 500, filename=paste(store.at,"/", sub(".wav","",file), ".jpg", sep="")) } # compressed images
if(plot.as=="tiff"|plot.as=="tif"){grDevices::tiff(width=5000, height=3500, units="px", res=500, filename=paste(store.at, "/", sub(".wav", "", file), ".tif", sep="")) } # uncompressed images
if(add.points==FALSE){
seewave::spectro(Wav, f=f, wl=wl, ovlp=ovlp, osc=F, scale=F, grid=F,
main= sub(".wav", "", file), flim=flim, tlim=tlim,
cont=add.contour,contlevels=seq(-dBlevel,-dBlevel,1),lwd=lwd,
collevels=seq(-back.amp, 0, 0.1))
} # end contour
if(add.points==TRUE){
seewave::spectro(Wav, f=f, wl=wl, ovlp=ovlp, osc=F, scale=F, grid=F,
main= sub(".wav", "", file), flim=flim, tlim=tlim,
collevels=seq(-back.amp, 0, 0.1))
if(add.contour==TRUE){graphics::polygon(x=con$x, y=con$y, lwd=lwd)}
graphics::points(x=(SM$Time)/mag.time, y=SM$Frequency.values, pch=21, col="black", bg="red", cex=1.1)
graphics::text(x=(SM$Time)/mag.time, y=SM$Frequency.values,cex=0.7, labels=SM$SM.numbers, pos=c(3, rep(2, dim(SM1)[1]), 1, rep(4, dim(SM2)[1]))) ## SM numbers
} # end points
grDevices::dev.off()
} # end plot
# Export semilandmark coordinates as TPS file
if(!is.null(TPS.file)){
lmline <- paste("LM=", dim(SM)[1], sep="")
write(lmline, TPS.path, append = TRUE)
utils::write.table(SM[,4:3], TPS.path, col.names = FALSE, row.names = FALSE, append = TRUE)
idline <- paste("ID=", sub(".wav", "", file), sep="")
write(idline, TPS.path, append = TRUE)
write("", TPS.path, append = TRUE)
rm(lmline, idline, SM)
} # end tps file
} # end loop
# Assign names to arrays' third dimension
if(add.points==TRUE){
dimnames(coord2D)[[3]] <- sub(".wav","", list.files(wav.at, pattern = ".wav"))} # end add.poins = TRUE
} # End twoDshape
if(analysis.type == "twoDshape" && add.points == TRUE){ coord <- coord2D }
else( coord <- NULL )# twoDshape results
if(analysis.type == "threeDshape"){ coord <- coord3D } # threeDshape results
results <- coord
} # End eigensound function
Any scripts or data that you put into this service are public.
Add the following code to your website.
For more information on customizing the embed code, read Embedding Snippets.