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.
The 44-byte header structure for a simple wave file is shown below.
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. |
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]
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)
This audio isn’t very interesting, but it can be played using the
audio
package
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-friendlyAll 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)