riff- RIFF parser for Haskell

Copyright(c) Robert Massaioli, 2014
Safe HaskellSafe-Inferred




The RIFF module allows the parsing of RIFF files in pure Haskell. It was written to be as efficient and as simple as possible.

You can parse a RIFF file using the methods provided in this module. For example, if you wanted to parse a RIFF file and print it out then you could:

main = withRiffFile "path/to/file.riff" print

And that will print the RIFF file, in gory details, to the screen. You can also use this module to create a RIFF file in pure Haskell starting with a RiffFile and building it up until you are ready to write it out to a ByteString or Disk. For example, this is how you might construct a RIFF file to be written out:

import Data.Riff
import qualified Data.ByteString.Lazy as BL

riffFile = RiffFile RIFX "EXPL" children

children = 
   [ RiffChunkChild "fst " $ BL.pack [1..11]
   , RiffChunkChild "snd " $ BL.pack [11..100]

main = assembleRiffFile "example.riff" riffFile

As you can see it is a very simple API that lets you write out data into Riff Files. Have a play around with with the examples until you can see how it works and fits together.


RIFF File Data Representaion

data RiffFile Source

This is our representation of a RIFF file. These files all have a format type and are composed by one or more nestable data Chunks which we represent with a RiffChunk.




riffFileType :: RiffFileType

The type of RIFF file.

riffFileFormatType :: RiffId

An ID representing the type of data contained within.

riffFileChildren :: [RiffChunk]

The chunks that make up the file.


Eq RiffFile 
Show RiffFile 
Binary RiffFile

A binary instance of a RiffFile.

type RiffChunkSize = Word32 Source

A Riff file is made up exclusively of Riff Chunks and each chunk, as the second piece of data in the chunk, contains it's size. The size never includes the first 8 bytes of the chunk, which are the Chunk Id and the Chunk Size but, in the case of a nested chunk, it does include the four bytes of the Chunk FormType Id.

According to the specification a chunk size must be represented by four bytes of data. This means that the maximum number of bytes that can be present in a RIFF file is:

2 ^ 32 + 8 = 4294967304 bytes or ~4GB 

If your raw data is larger than that then this file format cannot support it.

data RiffFileType Source

There are only two different types of RIFF file: RIFF and RIFX and the difference is in the way that data is encoded inside them.



This is the most common type of RIFF file and is in little endian format.


This is a rare riff format that uses big endian encoding (otherwise known as Motorolla byte order)

data RiffChunk Source

A RiffFile is just an alias for a RiffChunk. A RiffFile is merely a nested collection of RiffChunks where the first element must be a list of chunks with the ID RIFF or RIFX.

type RiffId = String Source

A RiffId is just a four character string (FourCC). It is usually (but by no means always) chosen to be something that is human readable when converted to ASCII. Please note that attempting to assemble a riff file with a RIFF ID that is not exactly four characters long will result in the RiffId being modified in the output stream to be four characters long. If the ID is too short then it will be padded with space (' ') characters and if it is too long then it will be truncated at four characters.

type RiffData = ByteString Source

The data in a riff file is just a stream of bytes.

type ParseError = (ByteOffset, String) Source

Represents an error in the parsing of a Riff File. It contains the location in the file that we read up to and a message of what went wrong.

Reading (parsing) RIFF Files

withRiffFile Source


:: FilePath

The file that will be read.

-> (Either ParseError RiffFile -> IO ())

An action to perform on the potentialy parsed file

-> IO ()

The resultant IO action.

Given a FilePath you can provide a function that will be given either a ParseError or an actual RiffFile to process. It is important to note that you should do all of the processing of the RiffFile in the provided action because the file will be closed at the end of this function.

parseRiffFileStream :: ByteString -> Either ParseError RiffFile Source

You can parse a raw ByteString and try and convert it into a RiffFile. This will give a best attempt at parsing the data and, if success is not possible, will give you a ParseError.

getRiffFile :: Get RiffFile Source

A binary instance of RiffFile so that you can parse one wherever you find it.

Writing (assembling) RIFF Files

assembleRiffFile Source


:: FilePath

The location on the filesystem to save the RiffFile.

-> RiffFile

The in-memory representation of a RiffFile to be saved.

-> IO ()

Writing to disk is an IO operatin and we return no results.

Given a file path and a RiffFile representation this will allow you to safely write out a RiffFile to disk. This allows you to save anything that you like in a RiffFile. Just remember that the maximum file size of a RiffFile is bounded by the maximum size of a 32bit integer. The behaviour of this function, should you give it too much data, is undefined.

assembleRiffFileStream Source


:: RiffFile

The RIFF file to be written out.

-> ByteString

The resultant stream of bytes representing the file.

Assembles a RiffFile into it's representation in a Lazy ByteString.

putRiffFile Source


:: RiffFile

The riff file to write out to the stream.

-> Put

The Put monad that will do the writing.

A Binary put instance so that you can write a riff file right out to any stream.