Introduction
A note doesn’t have to be a sine wave.
Square, Triangle and sawtooth waves are also common and have different sound characteristics.
Utilities developed in prior posts
Click to reveal ADSR profiling and other utilities
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
#' Create an ADSR profile of the given length - using only linear segments
#'
#' @param N length of profile (integer)
#' @param a,d,r fractions of the profile devoted to attack, decay and release
#' respectively
#' @param s the level of sustain in range [0,1]. The duration of the sustain
#' will be the remaining fraction after attack, decay and release
#' are accounted for
#'
#' @return numeric vector in range [0,1] of length N which defines a
#' volumn envelope for the note.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
create_adsr_profile <- function(N, a, d, s, r) {
dur <- 1 - a - d - r
stopifnot(dur >= 0)
profile <- c(
seq(0, 1, length.out = a * N),
seq(1, s, length.out = d * N),
rep(s, length.out = dur * N),
seq(s, 0, length.out = r * N)
)
# Ensure we have the right number of elements in case of weird rounding
length(profile) <- N
profile[is.na(profile)] <- 0
profile
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Play a note
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
play_note <- function(samples) {
audio::play(samples, rate = 44100)
}
Create simple note with different waveforms
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create a note - sine wave
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
create_sine_wave <- function(freq, duration) {
t <- seq(duration * 44100)
samples <- sin((t) * 2*pi/ (44100/freq))
samples
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create a note - square wave
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
create_square_wave <- function(freq, duration) {
samples <- create_sine_wave(freq, duration)
samples <- ifelse(samples >=0, 1, -1)
samples
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create a note - triangular wave.
# Create as a cumsum() of a square wave.
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
create_triangle_wave <- function(freq, duration) {
period <- 44100/freq
wave <- c(
seq( 0, 1, length.out = (period/4 - 1)),
seq( 1, -1, length.out = (period/2 - 1)),
seq(-1, 0, length.out = (period/4 - 1))
)
rep(wave, length.out = 44100 * duration)
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Create a note - sawtooth wave
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
create_sawtooth_wave <- function(freq, duration) {
period <- 44100/freq
wave <- seq(-1, 1, length.out = period - 1)
rep(wave, length.out = 44100 * duration)
}
note_sine <- create_sine_wave(440, 1)
plot(note_sine[1:300], type = 'l')
title('Sine')
note_square <- create_square_wave(440, 1)
plot(note_square[1:300], type = 'l')
title('Square')
note_tri <- create_triangle_wave(440, 1)
plot(note_tri[1:300], type = 'l')
title('Triangle')
note_saw <- create_sawtooth_wave(440, 1)
plot(note_saw[1:300], type = 'l')
title('Sawtooth')
profile <- create_adsr_profile(44100, 0.2, 0.3, 0.8, 0.5)
play_note(note_sine * profile)
play_note(note_square * profile)
play_note(note_tri * profile)
play_note(note_saw * profile)
Sine
Square
Triangle
Sawtooth
World’s simplest R music system - with linear ADSR profile shaping + sawtooth waveform
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Octaves/Notes/Frequencies in an equal tempered scale
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
ets <- tibble(
freq = 440 * (2 ^ ((-57:50)/12)),
note = rep(c('C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'), 9),
octave = rep(0:8, each = 12),
mnote = ifelse(nchar(note) == 1, paste(note, octave, sep = "-"), paste(note, octave, sep = ""))
)
head(ets)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Note-to-Freq lookup
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
freq <- setNames(ets$freq, ets$mnote)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Function: Play a note of the given frequency for the given duration
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
profile <- create_adsr_profile(0.4 * 44100, 0.1, 0.25, 0.2, 0.6)
play_note <- function(freq, duration) {
samples <- create_sawtooth_wave(freq, duration)
samples <- samples * profile
audio::play(samples , rate = 44100)
}
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Mary had a little lamb.
# Format is "[Note]-[Octave]"
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
mhall <- c(
'A-4', 'G-4', 'F-4', 'G-4', 'A-4', 'A-4', 'A-4',
'G-4', 'G-4', 'G-4', 'A-4', 'C-5', 'C-5',
'A-4', 'G-4', 'F-4', 'G-4', 'A-4', 'A-4', 'A-4',
'A-4', 'G-4', 'G-4', 'A-4', 'G-4', 'F-4'
)
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# Play it
#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
for (note in mhall) {
play_note(freq[[note]], 0.4)
Sys.sleep(0.4)
}
Different waveforms sound different
The following two live captures of the same song at the same pitch with the same ADSR note shaping - only the note waveform is different.