The hardware and bandwidth for this mirror is donated by dogado GmbH, the Webhosting and Full Service-Cloud Provider. Check out our Wordpress Tutorial.
If you wish to report a bug, or if you are interested in having us mirror your free-software or open-source project, please feel free to contact us at mirror[@]dogado.de.

Handcrafting a WAV file

library(ctypesio)

WAV files

WAV files are a standard format for storing audio data.

This vignette will use the WAV file structure to write bespoke data as a WAV file, and then read it back in.

WAV file structure

The 44-byte header structure for a simple wave file is shown below.

WAV file structure
Positions Sample.Value Description
1 - 4 ‘RIFF’ Marks the file as a riff file. Characters are each 1 byte long.
5 - 8 File size (integer) Size of the overall file - 8 bytes, in bytes (32-bit integer). Typically, you’d fill this in after creation.
9 -12 “WAVE” File Type Header. For our purposes, it always equals “WAVE”.
13-16 ‘fmt’ Format chunk marker. Includes trailing space
17-20 16 Length of format data as listed above
21-22 1 Type of format (1 is PCM) - 2 byte integer
23-24 2 Number of Channels - 2 byte integer
25-28 44100 Sample Rate - 32 byte integer. Common values are 44100 (CD), 48000 (DAT). Sample Rate = Number of Samples per second, or Hertz.
29-32 176400 (Sample Rate * BitsPerSample * Channels) / 8.
33-34 4 (BitsPerSample * Channels) / 8. (1) 8 bit mono (2) - 8 bit stereo/16 bit mono (4) - 16 bit stereo
35-36 16 Bits per sample
37-40 ‘data’ data chunk header. Marks the beginning of the data section.
41-44 File size (data) Size of the data section.

Audio waveform

Create 2 seconds of random noise data to be played at 44.1 kHz.

Data values should be in range for a 16-bit integer i.e. [-32768, 32767]

audio <- as.integer(runif(44100 * 2, -32768, 32767))

Write the WAV file

Data is stored little-endian (which is the default endian mode for ctypesio)

wav_file <- tempfile(fileext = ".wav")
con <- file(wav_file, "wb")

write_utf8_raw(con, "RIFF")
write_uint32(con, length(audio) * 2 + 44 - 8) # total file length - 8
write_utf8_raw(con, "WAVE")
write_utf8_raw(con, "fmt ")
write_uint32(con, 16)                 # size of header so far
write_uint16(con, 1)                  # sample format = 2-byte integer
write_uint16(con, 1)                  # single channel
write_uint32(con, 44100)              # sample rate
write_uint32(con, 44100 * 16 * 1 / 8) # rate * bits * channels / 8
write_uint16(con, 2)                  # 16-bit mono audio
write_uint16(con, 16)                 # bits per sample
write_utf8_raw(con, 'data')
write_uint32(con, length(audio) * 2)  # sampled data size
write_int16(con, audio)

close(con)

Play the audio

This audio isn’t very interesting, but it can be played using the audio package

library(audio)
wav <- audio::load.wave(wav_file)
audio::play(wav)

Parsing a WAV file

This is just the reverse of writing the file.

con <- file(wav_file, "rb")

read_utf8_raw(con, 4)
#> [1] "RIFF"
read_uint32(con)      # total file length - 8
#> [1] 176436
read_utf8_raw(con, 4)
#> [1] "WAVE"
read_utf8_raw(con, 4)
#> [1] "fmt "
read_uint32(con)      # size of header above
#> [1] 16
read_uint16(con)      # sample format = 2-byte integer
#> [1] 1
read_uint16(con)      # single channel
#> [1] 1
read_uint32(con)      # sample rate
#> [1] 44100
read_uint32(con)      # rate * bits * channels / 8
#> [1] 88200
read_uint16(con)      # 16-bit mono audio
#> [1] 2
read_uint16(con)      # bits per sample
#> [1] 16
read_utf8_raw(con, 4)
#> [1] "data"
(data_size <- read_uint32(con))         # sampled data size
#> [1] 176400
data <- read_uint16(con, data_size / 2) # read in 16-bit samples

close(con)

ctypesio is pipe-friendly

All the write_*() functions in ctypesio return the connection object, so that the functions can piped together.

wav_file <- tempfile(fileext = ".wav")
con <- file(wav_file, "wb")

write_utf8_raw(con, "RIFF") |>
  write_uint32(length(audio) * 2 + 44 - 8) |> # total file length - 8
  write_utf8_raw("WAVE")           |>
  write_utf8_raw("fmt ")           |> 
  write_uint32(16)                 |> # size of header so far
  write_uint16(1)                  |> # sample format = 2-byte integer
  write_uint16(1)                  |> # single channel
  write_uint32(44100)              |> # sample rate
  write_uint32(44100 * 16 * 1 / 8) |> # rate * bits * channels / 8
  write_uint16(2)                  |> # 16-bit mono audio
  write_uint16(16)                 |> # bits per sample
  write_utf8_raw('data')           |> 
  write_uint32(length(audio) * 2)  |> # sampled data size
  write_int16(audio)

close(con)

These binaries (installable software) and packages are in development.
They may not be fully stable and should be used with caution. We make no claims about them.
Health stats visible at Monitor.