Reading and writing WAV audio files

The following example shows simple reading and writing (and later processing) of WAV audio files using File.ReadAllBytes, File.WriteAllBytes and BitConverter.

> open System.IO;;
> File.ReadAllBytes;;
val it : string -> byte [] = <fun:clo@4>
> File.WriteAllBytes;;
val it : string * byte [] -> unit = <fun:clo@5-1>
> BitConverter.ToInt16;;
val it : byte [] * int -> int16 = <fun:clo@6-2>
> let a = File.ReadAllBytes("testin.wav");;
val it : byte [] =
  [|82uy; 73uy; 70uy; 70uy; 4uy; 117uy; 20uy; 0uy; 87uy; 65uy; 86uy; 69uy;
    102uy; 109uy; 116uy; 32uy; 16uy; 0uy; 0uy; 0uy; 1uy; 0uy; 2uy; 0uy; 68uy;
    172uy; 0uy; 0uy; 16uy; 177uy; 2uy; 0uy; 4uy; 0uy; 16uy; 0uy; 100uy; 97uy;
    116uy; 97uy; 224uy; 116uy; 20uy; 0uy; 154uy; 255uy; 170uy; 255uy; 116uy;
    255uy; 136uy; 255uy; 97uy; 255uy; 117uy; 255uy; 90uy; 255uy; 107uy; 255uy;
    76uy; 255uy; 101uy; 255uy; 76uy; 255uy; 104uy; 255uy; 78uy; 255uy; 103uy;
    255uy; 108uy; 255uy; 101uy; 255uy; 128uy; 255uy; 104uy; 255uy; 111uy;
    255uy; 93uy; 255uy; 87uy; 255uy; 75uy; 255uy; 66uy; 255uy; 50uy; 255uy;
    63uy; 255uy; 23uy; 255uy; 22uy; 255uy; 249uy; 254uy; ...|]

File.ReadAllBytes creates an array which contains all bytes of the file. The binary content must then be identified and transferred to an internal data structure.

> open System.Text;;
> a.[ 0.. 3] = Encoding.ASCII.GetBytes("RIFF");;
val it : bool = true

Individual parameters are parsed by passing sliced parts of the array to BitConverter.ToInt32 and BitConverter.ToInt16. Endianness is applied according host settings.

> BitConverter.IsLittleEndian;;  
val it : bool = true
> let ChunkSize    = BitConverter.ToInt32(a.[4..7], 0);;

val ChunkSize : int = 1340676

> let SubChunk1Size = BitConverter.ToInt32(a.[16..19], 0);;

val SubChunk1Size : int = 16

> let AudioFormatVal = BitConverter.ToInt16(a.[20..21], 0);;

val AudioFormatVal : int16 = 1s

To generate an array of samples it is necessary to combine two bytes into one 16 bit sample integer. The sequence function Seq.breakBy is used to split the array into an array of 2 element arrays (see also here), these two element arrays are then converted into 16 bit integers.

> #r "seq1.dll";;

--> Referenced '/home/ootoo/Projekte/FSharp/2sharp4u/2011-10-29 Reading and writing WAV audio files/seq1.dll'

> open Microsoft.FSharp.Collections;;
> let data = a.[(SubChunk2Start+8)..(SubChunk2Start+8+SubChunk2Size-1)];;

val data : byte [] =
  [|154uy; 255uy; 170uy; 255uy; 116uy; 255uy; 136uy; 255uy; 97uy; 255uy; 117uy;
    255uy; 90uy; 255uy; 107uy; 255uy; 76uy; 255uy; 101uy; 255uy; 76uy; 255uy;
    104uy; 255uy; 78uy; 255uy; 103uy; 255uy; 108uy; 255uy; 101uy; 255uy; 128uy;
    255uy; 104uy; 255uy; 111uy; 255uy; 93uy; 255uy; 87uy; 255uy; 75uy; 255uy;
    66uy; 255uy; 50uy; 255uy; 63uy; 255uy; 23uy; 255uy; 22uy; 255uy; 249uy;
    254uy; 239uy; 254uy; 220uy; 254uy; 226uy; 254uy; 209uy; 254uy; 242uy;
    254uy; 215uy; 254uy; 236uy; 254uy; 209uy; 254uy; 225uy; 254uy; 213uy;
    254uy; 218uy; 254uy; 219uy; 254uy; 225uy; 254uy; 235uy; 254uy; 237uy;
    254uy; 0uy; 255uy; 251uy; 254uy; 18uy; 255uy; 6uy; 255uy; 28uy; 255uy; 7uy;
    255uy; 22uy; 255uy; ...|]
> let Sample16BitLE2Int i = int(BitConverter.ToInt16((Seq.toArray i), 0));;

val Sample16BitLE2Int : seq<byte> -> int

> let Samples = data |> Seq.breakByV1 2 |> Seq.map Sample16BitLE2Int;;

val Samples : seq<int>

Below is the complete source code:

open System
open System.IO
open System.Text

// https://ccrma.stanford.edu/courses/422/projects/WaveFormat/

type WavAudioFormat = WavPCM
exception WavUnknownFormat
exception WavUnknownNumChannels
exception WavUnknownBitsPerSample

let Sample16BitLE2Int i = int(BitConverter.ToInt16((Seq.toArray i), 0))
let Int2Sample16BitLE s = BitConverter.GetBytes(int16(s))

let breakBy windowSize (source: seq<_>) =    
    seq { let arr = Array.create windowSize (Seq.nth 0 source)
          let i = ref 0
          use e = source.GetEnumerator()
          while e.MoveNext() do
              arr.[!i] <- e.Current
              i := !i + 1
              if !i = windowSize then
                yield Array.init windowSize (fun j -> arr.[j])
                i := 0
        }

type WavFile() =
  let mutable AudioFormat:WavAudioFormat = WavPCM
  let mutable NumChannels:int = 0
  let mutable SampleRate:int = 0
  let mutable BitsPerSample:int = 0
  let mutable Samples:seq<int> = seq [||]

  member this.Read(filename:string) =
    let a = File.ReadAllBytes(filename)
    if a.Length < 40 then
      raise(WavUnknownFormat)
    if a.[ 0.. 3] <> Encoding.ASCII.GetBytes("RIFF")
    || a.[ 8..11] <> Encoding.ASCII.GetBytes("WAVE")
    || a.[12..15] <> Encoding.ASCII.GetBytes("fmt ") then
      raise(WavUnknownFormat)

    let ChunkSize    = BitConverter.ToInt32(a.[4..7], 0)
    let SubChunk1Size = BitConverter.ToInt32(a.[16..19], 0)
    let AudioFormatVal = BitConverter.ToInt16(a.[20..21], 0)
    AudioFormat <- match AudioFormatVal with
                   | 1s -> WavPCM
                   | _ -> raise(WavUnknownFormat)

    NumChannels <- int(BitConverter.ToInt16(a.[22..23], 0))
    if NumChannels < 1 || NumChannels > 2 then raise(WavUnknownNumChannels)

    SampleRate <- int(BitConverter.ToInt32(a.[24..27], 0))

    BitsPerSample <- int(BitConverter.ToInt16(a.[34..35], 0))
    if BitsPerSample % 8 <> 0 then raise(WavUnknownBitsPerSample)

    let SubChunk2Start = 12 + 8 + SubChunk1Size
    if a.[SubChunk2Start..(SubChunk2Start+3)] <>
       Encoding.ASCII.GetBytes("data") then
      raise(WavUnknownFormat)

    let SubChunk2Size = BitConverter.ToInt32(
                          a.[(SubChunk2Start+4)..(SubChunk2Start+7)], 0)
    let data = a.[(SubChunk2Start+8)..(SubChunk2Start+8+SubChunk2Size-1)]
    Samples <- match BitsPerSample with
               | 16 -> data |> breakBy 2 |> Seq.map Sample16BitLE2Int
               | _ -> raise(WavUnknownBitsPerSample)

  member this.Write(filename:string) =
    let NumSamples = Seq.length Samples

    let mutable bytes = Encoding.ASCII.GetBytes "RIFF"
    bytes <- Array.append bytes (BitConverter.GetBytes
      (44 + (NumSamples * BitsPerSample/8)))
    bytes <- Array.append bytes (Encoding.ASCII.GetBytes "WAVE")

    bytes <- Array.append bytes (Encoding.ASCII.GetBytes "fmt ")
    match AudioFormat with
    | WavPCM ->
      bytes <- Array.append bytes (BitConverter.GetBytes 16)
      bytes <- Array.append bytes (BitConverter.GetBytes (int16 1))
      bytes <- Array.append bytes (BitConverter.GetBytes (int16 NumChannels))
      bytes <- Array.append bytes (BitConverter.GetBytes SampleRate)
      bytes <- Array.append bytes (BitConverter.GetBytes
        (SampleRate*NumChannels*BitsPerSample/8))
      bytes <- Array.append bytes (BitConverter.GetBytes
        (int16 (NumChannels*BitsPerSample/8)))
      bytes <- Array.append bytes (BitConverter.GetBytes (int16 BitsPerSample))

      bytes <- Array.append bytes (Encoding.ASCII.GetBytes "data")
      bytes <- Array.append bytes (BitConverter.GetBytes
        (NumSamples * NumChannels * BitsPerSample/8))

      let data = Samples |> Seq.map Int2Sample16BitLE
                 |> Seq.collect (fun i -> i) |> Seq.toArray
      bytes <- Array.append bytes data

    File.WriteAllBytes(filename, bytes)

  member this.Print():unit =
    printfn "RIFF ChunkSize= ";

    printf "WAVE AudioFormat=";
    if AudioFormat = WavPCM then printf "PCM "
    else printf "unknown "

    printfn "NumChannels=%d SampleRate=%d BitsPerSample=%d "
      NumChannels SampleRate BitsPerSample

    let NumSamples = Seq.length Samples
    printfn "%d samples, %dmin %ds %dms playtime" NumSamples
      (NumSamples/SampleRate/60)
      ((NumSamples/SampleRate)%60)
      (((NumSamples%SampleRate)*1000)/SampleRate)

  member this.GetSamples() = Samples

let args = System.Environment.GetCommandLineArgs()
if (Array.length args) = 3 then
  let infilename = args.[1]
  let outfilename = args.[2]
  let w = new WavFile()
  w.Read(infilename)
  w.Print()
  w.Write(outfilename)
else
  printf "wav1.exe <input filename.wav> <output filename.wav>\n"
About these ads
This entry was posted in Getting Started, IO. Bookmark the permalink.

One Response to Reading and writing WAV audio files

  1. Pingback: Digital Signal Processing #1 | 2#4u

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s