diff --git a/DESCRIPTION b/DESCRIPTION index a31e2c4..4ca4331 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -1,6 +1,6 @@ Package: hrep Title: Harmony Representations -Version: 0.11.1 +Version: 0.12.1 Authors@R: person("Peter", "Harrison", email = "pmc.harrison@gmail.com", role = c("aut", "cre")) Description: This package provides utilities for representing and manipulating chord sequences for perceptually informed harmony modelling. diff --git a/NAMESPACE b/NAMESPACE index f8861eb..d615c05 100644 --- a/NAMESPACE +++ b/NAMESPACE @@ -164,6 +164,7 @@ S3method(represent,default) S3method(represent,list) S3method(represent,vec) S3method(sample_rate,numeric) +S3method(save_wav,default) S3method(save_wav_sox,coded_vec) S3method(save_wav_sox,default) S3method(save_wav_sox,fr_chord) @@ -299,9 +300,11 @@ export(pi_chord_type) export(pi_to_pc) export(pitch) export(play_sox) +export(play_wav) export(register_chord_quality) export(represent) export(sample_rate) +export(save_wav) export(save_wav_sox) export(smooth_pc_spectrum) export(smooth_pi_spectrum) diff --git a/NEWS.md b/NEWS.md index f62ebda..b061d72 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,3 +1,11 @@ +# hrep 0.12.1 + +- Exported `play_wav`. + +# hrep 0.12.0 + +- Implemented `save_wav` and `play_wav`, a more streamlined and faster alternative to `save_wav_sox` and `play_wav_sox`. + # hrep 0.11.1 - Improved documentation for sparse_pi_spectrum, sparse_pc_spectrum, and sparse_fr_spectrum. diff --git a/R/play-sox.R b/R/play-sox.R index 7b6a956..73d4449 100644 --- a/R/play-sox.R +++ b/R/play-sox.R @@ -4,6 +4,8 @@ #' #' The sound is synthesised using \code{\link{save_wav_sox}} #' and saved to a temporary file, which is then played from the R session. +#' This method is generally slower than \code{\link{play_wav}} +#' but it provides more features. #' #' @note #' The command-line sound-processing program sox @@ -14,6 +16,8 @@ #' @param x Object to play (see \code{\link{save_wav_sox}} for valid options). #' @param ... Further parameters to pass to \code{\link{save_wav_sox}}. #' +#' @seealso \code{\link{play_wav}} +#' #' @export play_sox <- function(x, ...) { UseMethod("play_sox") diff --git a/R/save-wav-sox.R b/R/save-wav-sox.R index 89730cb..662ee31 100644 --- a/R/save-wav-sox.R +++ b/R/save-wav-sox.R @@ -3,6 +3,9 @@ #' Saves object to a wav file using sox #' (\url{http://sox.sourceforge.net/}). #' +#' This method is generally slower than \code{\link{save_wav}} +#' but it provides more features. +#' #' @note #' The command-line sound-processing program sox #' (\url{http://sox.sourceforge.net/}) @@ -17,6 +20,8 @@ #' #' @param ... Parameters passed to methods. #' +#' @seealso \code{\link{save_wav}} +#' #' @rdname save_wav_sox #' @export save_wav_sox <- function(x, file, ...) { diff --git a/R/sparse-fr-spectrum.R b/R/sparse-fr-spectrum.R index e3ff769..03a49cb 100644 --- a/R/sparse-fr-spectrum.R +++ b/R/sparse-fr-spectrum.R @@ -48,8 +48,7 @@ is.sparse_fr_spectrum <- function(x) { #' the second element should be labelled "amplitude", #' and correspond to a numeric vector of amplitudes. #' -#' @param ... Further arguments passed to \code{\link{expand_harmonics}()}, -#' depending on the method invoked. +#' @inheritDotParams expand_harmonics #' #' @return An object of class \code{sparse_fr_spectrum}. #' diff --git a/R/sparse-pc-spectrum.R b/R/sparse-pc-spectrum.R index 7091210..613926b 100644 --- a/R/sparse-pc-spectrum.R +++ b/R/sparse-pc-spectrum.R @@ -55,8 +55,7 @@ is.sparse_pc_spectrum <- function(x) { #' #' @param x Input sonority. #' -#' @param ... Further arguments passed to \code{\link{expand_harmonics}()}, -#' depending on the method invoked. +#' @inheritDotParams expand_harmonics #' #' @return An object of class \code{sparse_pc_spectrum}. #' diff --git a/R/sparse-pi-spectrum.R b/R/sparse-pi-spectrum.R index a8ff6d5..8ad5a51 100644 --- a/R/sparse-pi-spectrum.R +++ b/R/sparse-pi-spectrum.R @@ -55,8 +55,7 @@ is.sparse_pi_spectrum <- function(x) { #' #' @param x Input sonority. #' -#' @param ... Further arguments passed to \code{\link{expand_harmonics}()}, -#' depending on the method invoked. +#' @inheritDotParams expand_harmonics #' #' @return An object of class \code{sparse_pi_spectrum}. #' diff --git a/R/wave.R b/R/wave.R index c593023..4a08539 100644 --- a/R/wave.R +++ b/R/wave.R @@ -34,7 +34,8 @@ print.wave <- function(x, ...) { #' The sample rate can be accessed using the \code{\link{sample_rate}} accessor. #' #' @param x Input object. -#' @param ... Arguments to be passed to \code{\link{sparse_fr_spectrum}}, as appropriate. +#' +#' @inheritDotParams expand_harmonics #' #' @rdname wave #' @export @@ -43,13 +44,31 @@ wave <- function(x, ...) { } #' @param length_sec (Numeric scalar) Length of the output wave, in seconds. +#' #' @param sample_rate (Integerish scalar) The desired sample rate. #' +#' @param rise_length +#' (Numeric scalar) +#' Chord fade-in time (seconds). +#' +#' @param fade_length +#' (Numeric scalar) +#' Chord fade-out time (seconds). +#' #' @rdname wave #' @export -wave.default <- function(x, length_sec = 1, sample_rate = 44100, ...) { +wave.default <- function(x, + length_sec = 1, + sample_rate = 44100, + rise_length = 0, + fade_length = 0, + ...) { x <- sparse_fr_spectrum(x, ...) - wave(x, length_sec = length_sec, sample_rate = sample_rate) + wave(x, + length_sec = length_sec, + rise_length = rise_length, + fade_length = fade_length, + sample_rate = sample_rate) } #' @export @@ -57,22 +76,52 @@ wave.wave <- function(x, ...) x #' @rdname wave #' @export -wave.sparse_fr_spectrum <- function(x, length_sec = 1, sample_rate = 44100, ...) { +wave.sparse_fr_spectrum <- function( + x, + length_sec = 1, + sample_rate = 44100, + rise_length = 0, + fade_length = 0, + ... +) { checkmate::qtest(length_sec, "N1[0)") checkmate::qtest(sample_rate, "X1[1)") + stopifnot(rise_length <= length_sec, + fade_length <= length_sec) frequency <- freq(x) amplitude <- amp(x) num_samples <- sample_rate * length_sec time <- seq(from = 0, to = length_sec, length.out = num_samples + 1)[- (num_samples + 1)] - mapply( + wave <- mapply( function(frequency, amplitude) { amplitude * sin(2 * pi * frequency * time) }, frequency, amplitude ) %>% - rowMeans() %>% - .wave(sample_rate) + rowSums() %>% + .wave(sample_rate) %>% + add_fades(rise_length, fade_length, sample_rate, num_samples) +} + +add_fades <- function(wave, rise_length, fade_length, sample_rate, num_samples) { + if (rise_length != 0) { + rise_n_samples <- round(rise_length * sample_rate) + stopifnot(rise_n_samples <= num_samples) + if (rise_n_samples > 0) { + ind <- 1:rise_n_samples + wave[ind] <- wave[ind] * seq(from = 0, to = 1, length.out = rise_n_samples) + } + } + if (fade_length != 0) { + fade_n_samples <- round(fade_length * sample_rate) + stopifnot(fade_n_samples <= num_samples) + if (fade_n_samples > 0) { + ind <- seq(to = num_samples, length.out = fade_n_samples) + wave[ind] <- wave[ind] * seq(from = 1, to = 0, length.out = fade_n_samples) + } + } + wave } #' @export @@ -90,3 +139,104 @@ plot.wave <- function(x, ggplot = FALSE, xlab = "Time (seconds)", ylab = "Displa plot(time, x, xlab = xlab, ylab = ylab, type = "l", ylim = ylim) } } + +#' Save wav file +#' +#' Saves object to a wav file by converting to the \code{\link{wave}} representation +#' and then writing to a wav file. +#' +#' @param x Object to save; currently only individual chords are supported. +#' Chords are coerced to a \code{\link{wave}} representation. +#' +#' @param file (Character scalar) Output file. +#' +#' @param amplitude +#' (Numeric scalar) +#' The wave is multiplied by this number before exporting. +#' The resulting wave should fall completely within the range [-1, 1]. +#' +#' @param bit_depth +#' (Integer scalar) +#' The bit depth of the exported audio. +#' +#' @param end_pad +#' (Numeric scalar) +#' Duration of silence (seconds) appended to the end of the audio file, +#' used to avoid clicks and other artifacts. +#' +#' @inheritParams wave +#' @inheritDotParams expand_harmonics +#' +#' @seealso \code{\link{save_wav_sox}} +#' +#' @rdname save_wav +#' @export +save_wav <- function( + x, + file, + amplitude = 0.1, + bit_depth = 16L, + length_sec = 1, + fade_length = 0.1, + rise_length = 0.1, + end_pad = 0.05, + ... +) { + UseMethod("save_wav") +} + +#' @export +save_wav.default <- function( + x, + file, + amplitude = 0.1, + bit_depth = 16, + length_sec = 1, + fade_length = 0.1, + rise_length = 0.1, + end_pad = 0.05, + ... +) { + wave <- wave( + x, + length_sec = length_sec, + fade_length = fade_length, + rise_length = rise_length, + ... + ) + scale <- 2 ^ (bit_depth - 1) + vector <- round(as.numeric(wave) * amplitude * scale) + if (any(vector < - scale | vector >= scale)) { + stop(sprintf("the wave's maximum value was approximately %.2f times too big for exporting, ", + max(abs(vector / scale))), + "consider reducing amplitude by at least this value") + } + vector <- c(vector, rep(0, times = round(end_pad * sample_rate(wave)))) + wave_2 <- tuneR::Wave( + left = vector, + right = vector, + samp.rate = sample_rate(wave), + bit = bit_depth + ) + tuneR::writeWave(wave_2, file) +} + +#' Play wav +#' +#' Plays a chord as a wave file. +#' +#' @param x Chord to save. +#' +#' @inheritParams tuneR::play +#' @inheritDotParams save_wav +#' @inheritDotParams expand_harmonics +#' +#' @seealso \code{\link{play_sox}} +#' +#' @export +play_wav <- function(x, player = "play", ...) { + file <- tempfile(fileext = ".wav") + save_wav(x, file, ...) + tuneR::play(file, player = player) + invisible(file.remove(file)) +} diff --git a/man/play_sox.Rd b/man/play_sox.Rd index ab83af7..5269c97 100644 --- a/man/play_sox.Rd +++ b/man/play_sox.Rd @@ -17,6 +17,8 @@ Plays a sound using the command-line tool sox. \details{ The sound is synthesised using \code{\link{save_wav_sox}} and saved to a temporary file, which is then played from the R session. +This method is generally slower than \code{\link{play_wav}} +but it provides more features. } \note{ The command-line sound-processing program sox @@ -24,3 +26,6 @@ The command-line sound-processing program sox must be installed and available on the command line, making available the commands \code{sox} and \code{play}. } +\seealso{ +\code{\link{play_wav}} +} diff --git a/man/play_wav.Rd b/man/play_wav.Rd new file mode 100644 index 0000000..9f97341 --- /dev/null +++ b/man/play_wav.Rd @@ -0,0 +1,46 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/wave.R +\name{play_wav} +\alias{play_wav} +\title{Play wav} +\usage{ +play_wav(x, player = "play", ...) +} +\arguments{ +\item{x}{Chord to save.} + +\item{player}{(Path to) a program capable of playing a wave file by invocation from the command line. + If under Windows and no player is given, \dQuote{mplay32.exe} or \dQuote{wmplayer.exe} + (if the former does not exists as under Windows 7) will be chosen as the default.} + +\item{...}{ + Arguments passed on to \code{\link[=save_wav]{save_wav}}, \code{\link[=expand_harmonics]{expand_harmonics}} + \describe{ + \item{\code{file}}{(Character scalar) Output file.} + \item{\code{amplitude}}{(Numeric scalar) +The wave is multiplied by this number before exporting. +The resulting wave should fall completely within the range [-1, 1].} + \item{\code{bit_depth}}{(Integer scalar) +The bit depth of the exported audio.} + \item{\code{end_pad}}{(Numeric scalar) +Duration of silence (seconds) appended to the end of the audio file, +used to avoid clicks and other artifacts.} + \item{\code{length_sec}}{(Numeric scalar) Length of the output wave, in seconds.} + \item{\code{fade_length}}{(Numeric scalar) +Chord fade-out time (seconds).} + \item{\code{rise_length}}{(Numeric scalar) +Chord fade-in time (seconds).} + \item{\code{num_harmonics}}{(Integerish scalar) +Number of harmonics (including the fundamental) to which +each tone should be expanded.} + \item{\code{roll_off}}{(Numeric scalar) Parametrises the amount of amplitude roll-off +in the harmonics, with greater values corresponding to higher roll-off.} + \item{\code{digits}}{Number of digits to which each partial's MIDI pitch should be rounded.} + }} +} +\description{ +Plays a chord as a wave file. +} +\seealso{ +\code{\link{play_sox}} +} diff --git a/man/save_wav.Rd b/man/save_wav.Rd new file mode 100644 index 0000000..fd41640 --- /dev/null +++ b/man/save_wav.Rd @@ -0,0 +1,61 @@ +% Generated by roxygen2: do not edit by hand +% Please edit documentation in R/wave.R +\name{save_wav} +\alias{save_wav} +\title{Save wav file} +\usage{ +save_wav( + x, + file, + amplitude = 0.1, + bit_depth = 16L, + length_sec = 1, + fade_length = 0.1, + rise_length = 0.1, + end_pad = 0.05, + ... +) +} +\arguments{ +\item{x}{Object to save; currently only individual chords are supported. +Chords are coerced to a \code{\link{wave}} representation.} + +\item{file}{(Character scalar) Output file.} + +\item{amplitude}{(Numeric scalar) +The wave is multiplied by this number before exporting. +The resulting wave should fall completely within the range [-1, 1].} + +\item{bit_depth}{(Integer scalar) +The bit depth of the exported audio.} + +\item{length_sec}{(Numeric scalar) Length of the output wave, in seconds.} + +\item{fade_length}{(Numeric scalar) +Chord fade-out time (seconds).} + +\item{rise_length}{(Numeric scalar) +Chord fade-in time (seconds).} + +\item{end_pad}{(Numeric scalar) +Duration of silence (seconds) appended to the end of the audio file, +used to avoid clicks and other artifacts.} + +\item{...}{ + Arguments passed on to \code{\link[=expand_harmonics]{expand_harmonics}} + \describe{ + \item{\code{num_harmonics}}{(Integerish scalar) +Number of harmonics (including the fundamental) to which +each tone should be expanded.} + \item{\code{roll_off}}{(Numeric scalar) Parametrises the amount of amplitude roll-off +in the harmonics, with greater values corresponding to higher roll-off.} + \item{\code{digits}}{Number of digits to which each partial's MIDI pitch should be rounded.} + }} +} +\description{ +Saves object to a wav file by converting to the \code{\link{wave}} representation +and then writing to a wav file. +} +\seealso{ +\code{\link{save_wav_sox}} +} diff --git a/man/save_wav_sox.Rd b/man/save_wav_sox.Rd index 421686b..8c62994 100644 --- a/man/save_wav_sox.Rd +++ b/man/save_wav_sox.Rd @@ -79,9 +79,16 @@ Only relevant when \code{spectrum_gain} is not \code{auto}.} Saves object to a wav file using sox (\url{http://sox.sourceforge.net/}). } +\details{ +This method is generally slower than \code{\link{save_wav}} +but it provides more features. +} \note{ The command-line sound-processing program sox (\url{http://sox.sourceforge.net/}) must be installed and available on the command line under the command \code{sox}. } +\seealso{ +\code{\link{save_wav}} +} diff --git a/man/sparse_fr_spectrum.Rd b/man/sparse_fr_spectrum.Rd index 11b3cee..9495320 100644 --- a/man/sparse_fr_spectrum.Rd +++ b/man/sparse_fr_spectrum.Rd @@ -33,8 +33,16 @@ the second element should be labelled "amplitude", and correspond to a numeric vector of amplitudes. }} -\item{...}{Further arguments passed to \code{\link{expand_harmonics}()}, -depending on the method invoked.} +\item{...}{ + Arguments passed on to \code{\link[=expand_harmonics]{expand_harmonics}} + \describe{ + \item{\code{num_harmonics}}{(Integerish scalar) +Number of harmonics (including the fundamental) to which +each tone should be expanded.} + \item{\code{roll_off}}{(Numeric scalar) Parametrises the amount of amplitude roll-off +in the harmonics, with greater values corresponding to higher roll-off.} + \item{\code{digits}}{Number of digits to which each partial's MIDI pitch should be rounded.} + }} } \value{ An object of class \code{sparse_fr_spectrum}. diff --git a/man/sparse_pc_spectrum.Rd b/man/sparse_pc_spectrum.Rd index 006af00..a30e623 100644 --- a/man/sparse_pc_spectrum.Rd +++ b/man/sparse_pc_spectrum.Rd @@ -21,8 +21,16 @@ sparse_pc_spectrum(x, ...) \arguments{ \item{x}{Input sonority.} -\item{...}{Further arguments passed to \code{\link{expand_harmonics}()}, -depending on the method invoked.} +\item{...}{ + Arguments passed on to \code{\link[=expand_harmonics]{expand_harmonics}} + \describe{ + \item{\code{num_harmonics}}{(Integerish scalar) +Number of harmonics (including the fundamental) to which +each tone should be expanded.} + \item{\code{roll_off}}{(Numeric scalar) Parametrises the amount of amplitude roll-off +in the harmonics, with greater values corresponding to higher roll-off.} + \item{\code{digits}}{Number of digits to which each partial's MIDI pitch should be rounded.} + }} \item{amplitude}{(Numeric vector) Vector of amplitudes to assign to each pitch. diff --git a/man/sparse_pi_spectrum.Rd b/man/sparse_pi_spectrum.Rd index b4660ea..c460155 100644 --- a/man/sparse_pi_spectrum.Rd +++ b/man/sparse_pi_spectrum.Rd @@ -24,8 +24,16 @@ sparse_pi_spectrum(x, ...) \arguments{ \item{x}{Input sonority.} -\item{...}{Further arguments passed to \code{\link{expand_harmonics}()}, -depending on the method invoked.} +\item{...}{ + Arguments passed on to \code{\link[=expand_harmonics]{expand_harmonics}} + \describe{ + \item{\code{num_harmonics}}{(Integerish scalar) +Number of harmonics (including the fundamental) to which +each tone should be expanded.} + \item{\code{roll_off}}{(Numeric scalar) Parametrises the amount of amplitude roll-off +in the harmonics, with greater values corresponding to higher roll-off.} + \item{\code{digits}}{Number of digits to which each partial's MIDI pitch should be rounded.} + }} \item{amplitude}{(Numeric vector) Vector of amplitudes to assign to each pitch. diff --git a/man/wave.Rd b/man/wave.Rd index 23ca3b7..f02b8a0 100644 --- a/man/wave.Rd +++ b/man/wave.Rd @@ -8,18 +8,47 @@ \usage{ wave(x, ...) -\method{wave}{default}(x, length_sec = 1, sample_rate = 44100, ...) +\method{wave}{default}( + x, + length_sec = 1, + sample_rate = 44100, + rise_length = 0, + fade_length = 0, + ... +) -\method{wave}{sparse_fr_spectrum}(x, length_sec = 1, sample_rate = 44100, ...) +\method{wave}{sparse_fr_spectrum}( + x, + length_sec = 1, + sample_rate = 44100, + rise_length = 0, + fade_length = 0, + ... +) } \arguments{ \item{x}{Input object.} -\item{...}{Arguments to be passed to \code{\link{sparse_fr_spectrum}}, as appropriate.} +\item{...}{ + Arguments passed on to \code{\link[=expand_harmonics]{expand_harmonics}} + \describe{ + \item{\code{num_harmonics}}{(Integerish scalar) +Number of harmonics (including the fundamental) to which +each tone should be expanded.} + \item{\code{roll_off}}{(Numeric scalar) Parametrises the amount of amplitude roll-off +in the harmonics, with greater values corresponding to higher roll-off.} + \item{\code{digits}}{Number of digits to which each partial's MIDI pitch should be rounded.} + }} \item{length_sec}{(Numeric scalar) Length of the output wave, in seconds.} \item{sample_rate}{(Integerish scalar) The desired sample rate.} + +\item{rise_length}{(Numeric scalar) +Chord fade-in time (seconds).} + +\item{fade_length}{(Numeric scalar) +Chord fade-out time (seconds).} } \description{ This function represents an object as class "wave".