# World's simplest R music system. Part 3 - Non-sinusoidal Waveforms

## 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)

## 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 = ""))
)

#~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
# 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.