[bmx] RIFF Reader/Writer by spacerat [ 1+ years ago ]

Started by BlitzBot, June 29, 2017, 00:28:40

Previous topic - Next topic

BlitzBot

Title : RIFF Reader/Writer
Author : spacerat
Posted : 1+ years ago

Description : This module allows you to manipulate RIFF files in blitz. The RIFF file format is a container format, like XML, used by other formats, including WAVE and AVI. Like XML it is hierarchical, but unlike XML it is a binary format, not a text format, and everything is stored as efficiently as possible, so that the header for each chunk of data (including the file itself) consists only of 8 bytes, 4 for a tag, another 4 for the size of the chunk. List chunks, which contain other chunks, have a further 4 bytes for the list ID.

The module is OO, but there are a few non-OO functions for those who like them. Currently, when reading, the entire file is read into memory at once, so you can't stream with the module, and similarly files are simply overwritten when saving a RIFF file. These might be a problem if you're working with large files, but for a simple custom file format (for example, a tile map) the module is sufficient.


Code :
Code (blitzmax) Select
SuperStrict

Rem
bbdoc: RIFF File Manipulator
about: The RIFF module allows easy read/write access to RIFF format files.
End Rem
Module JOE.RIFF


ModuleInfo "Version: 0.9.0"
ModuleInfo "Author: Joseph 'Spacerat' Atkins-Turkish"
ModuleInfo "License: Public Domain (see source for notes)"
ModuleInfo "History: 0.9.0 Release to blitzmax.com"

Rem
This module is public domain because it's pretty simple, but if you use it for anything you release
I would appreciate it if you send a link to your project to spacerat3004@gmail.com, just because I like to know (if) my stuff is being used.
EndRem

Import Brl.Filesystem
Import Brl.endianstream
Import Brl.linkedlist
Import brl.math



Rem
bbdoc: Basic RIFF Chunk type
about: The RIFF file structure is based on chunks, which all have a four character ID and contain binary data.
End Rem
Type RIFFChunk
Field ID:String
Field Data:TBank
Field Parent:RIFFList

Function CreateChunk:RIFFChunk(ID:String, Size:Int = 0)
Local n:RIFFChunk = New RIFFChunk
n.ID = FourChrString(ID)
n.Data = CreateBank()
Return n
End Function

Rem
bbdoc: Fill the chunk with data from a stream
EndRem
Method Fill(stream:TStream, datasize:Long)
Data = CreateBank()

local bs:TBankStream = GetNewDataStream()

bs.WriteString(stream.ReadString(datasize))
End Method

Rem
bbdoc: Get the size in bytes of the chunk's data
EndRem
Method GetSize:Long()
Return Data.Size()
End Method

Rem
bbdoc: Return a new stream for easy access to the chunk's data
EndRem
Method GetNewDataStream:TBankStream()
Return TBankStream.Create(Data)
EndMethod

Rem
bbdoc: Write the chunk to a stream
EndRem
Method Write(stream:TStream)
stream.WriteString(FourChrString(ID))
Local s:Long = GetSize()
WriteUnsignedInt32(stream, s)

stream.WriteBytes(Data.Lock(), s)
Data.Unlock()

'Extra null byte
If GetSize() / 2.0 <> ceil(GetSize() / 2.0)
stream.WriteByte(0)
EndIf

End Method

Rem
bbdoc: Set the ID of the chunk
EndRem
Method SetName(fourchrs:String)
ID = FourChrString(ID)
End Method



EndType

Rem
bbdoc: RIFF List type
about: The RIIF list is a special type of chunk which contains subchunks.
EndRem
Type RIFFList Extends RIFFChunk
Rem
bbdoc: The subchunk list.
about: This list contains all subchunks of the list in the order that they were read and will be written.
EndRem
Field Subchunks:TList = New TList
Field ChunkType:String

Function CreateList:RIFFList(FormType:String)
Local n:RIFFList = New RIFFList
n.ID = "LIST"
n.ChunkType = FourChrString(FormType)
Return n
End Function

Rem
bbdoc: Fill the chunk list with data from a stream
about: The stream data should consist of a a number of separate chunks in RIFF format.
EndRem
Method Fill(stream:TStream, datasize:Long)
While datasize > 4
Local nid:String = stream.ReadString(4)
Local nsize:Long = ReadUnsignedInt32(stream)

Local n:RIFFChunk

If nid = "LIST"
n = New RIFFList
RIFFList(n).ChunkType = stream.ReadString(4)
n.ID = nid
Else
n = New RIFFChunk
n.ID = nid
EndIf

n.ID = nid
n.Fill(stream, nsize)
AddChunk(n)

datasize:-(nsize + 8)
If nsize / 2.0 <> ceil(nsize / 2.0)
datasize:-1
stream.ReadByte()
EndIf

Wend
EndMethod
rem
bbdoc: Get the size of all chunks in the list, and the header
endrem
Method GetSize:Long()
Local Size:Long
For Local c:RIFFChunk = EachIn Subchunks
Size = Size + c.GetSize() + 8
Next

Return Size + 4
End Method

Rem
bbdoc: Write the list to a stream
EndRem
Method Write(stream:TStream)
stream.WriteString(FourChrString(ID))
WriteUnsignedInt32(stream, GetSize())
stream.WriteString(FourChrString(ChunkType))

For Local c:RIFFChunk = Eachin Subchunks
c.Write(stream)
Next

'Extra null byte
If GetSize() / 2.0 <> ceil(GetSize() / 2.0)
stream.WriteByte(0)
EndIf

End Method

Rem
bbdoc: Add a chunk to the end of the chunk list
EndRem
Method AddChunk(chunk:RIFFChunk)
Subchunks.AddLast(chunk)
chunk.Parent = Self
End Method

Rem
bbdoc: Add a new chunk containing string data
returns: Returns the new chunk.
EndRem
Method AddTag:RIFFChunk(name:String, str:String)
Local n:RIFFChunk = RIFFChunk.CreateChunk(name)
AddChunk(n)
n.GetNewDataStream().WriteString(str)
Return n
End Method

Rem
bbdoc: Add a new chunk to the list
EndRem
Method NewChunk:RIFFChunk(name:String)
Local n:RIFFChunk = RIFFChunk.CreateChunk(name)
AddChunk(n)
Return n
End Method


Rem
bbdoc: Add a new list to the list
EndRem
Method NewList:RIFFList(format:String)
Local n:RIFFList = RIFFList.CreateList(format)
AddChunk(n)
Return n
End Method

Rem
bbdoc: Find a chunk by its name
about: Searches for and returns the first chunk found with an ID equal to @name,
or in the case of list chunks, a chunk type equal to @name.

The search is not recursive, and will not search in subchunks of subchunks.
Endrem
Method FindChunk:RIFFChunk(name:String)
name = FourChrString(name)
For local c:RIFFChunk = EachIn Subchunks

If c.ID = name Return c
IF RIFFList(c)
if RIFFList(c).ChunkType = name Return c
End If

Next
End Method

Rem
bbdoc: Set the list type of the list.
EndRem
Method SetName(fourchrs:String)
ChunkType = FourChrString(ID)
End Method

Method GetNewDataStream:TBankStream()
Throw "Cannot create data streams for list chunks."
End Method

EndType
Rem
bbdoc: The RIFF File type
about: A RIFF file is essentially one large list chunk, with a number of subchunks/lists.
It is entirely possible to construct, save and load RIFF files just using RIFFList, but
this type exists for conveniance.
EndRem
Type RIFFFile Extends RIFFList

Function CreateFile:RIFFFile(FileFormat:String, LittleEndian:Int = True)
Local n:RIFFFile = New RIFFFile
n.ChunkType = FileFormat
If LittleEndian = 1
n.ID = "RIFF"
Else
n.ID = "RIFX"
End If
Return n
EndFunction

Rem
bbdoc: Load a RIFF file from a url
EndRem
Function Load:RIFFFile(url:Object)
Local stream:TStream = OpenStream(url)
If Not(stream) Return Null

Local n:RIFFFile = New RIFFFile

Local id:String = stream.ReadString(4)
Select id
Case "RIFF"
stream = LittleEndianStream(stream)
Case "RIFX"
stream = BigEndianStream(stream)
Default
Return Null
End Select

n.ID = id
Local nsize:Long = ReadUnsignedInt32(stream)
n.ChunkType = stream.ReadString(4)

n.Fill(stream, nsize)

Return n
End Function

Rem
bbdoc: Save the file to a stream or URL
about: Files are not automatically created.
EndRem
Method Save(url:Object)
Local s:TStream = OpenStream(url)
If s = Null
' Print "Save error: null stream"
Return
End If
Write(s)
End Method

End Type

Rem
bbdoc: Create and initialise a new RIFF chunk
about: The @ID should be four characters, and is trimmed or padded otherwise.
EndRem
Function CreateRIFFChunk:RIFFChunk(ID:String, parent:RIFFList = Null)
Local c:RIFFChunk = RIFFChunk.CreateChunk(ID, 0)
If (parent) parent.AddChunk(c)
Return c
EndFunction

Rem
bbdoc: Create and initialise a new RIFF LIST
about: The @FormType should be four characters, and is trimmed or padded otherwise.
EndRem
Function CreateRIFFList:RIFFList(FormType:String, parent:RIFFList = Null)
Local c:RIFFList = RIFFList.CreateList(FormType)
If (parent) parent.AddChunk(c)
Return c
Endfunction

Rem
bbdoc: Create and initialise a new RIFF File
about: The @FileFormat should be four characters, and is trimmed or padded otherwise.

If @LittleEndian is true the file is a RIFF file, else it is a RIFX file.
EndRem
Function CreateRIFFFile:RIFFFile(FileFormat:String, LittleEndian:Int = True)
Return RIFFFile.CreateFile(FileFormat, LittleEndian)
EndFunction

Rem
bbdoc: Load a RIFF file from a url
EndRem
Function LoadRIFFFile:RIFFFile(url:Object)
Return RIFFFile.Load(url)
End Function

Rem
Some extra utility functions required for the module. You also might find them useful, espectially read/write unsignedInt32.
Also please let me know if you can think of a better way of doing it that doesn't involve longs.
EndRem

Function FourChrString:String(str:String)
If str.Length = 4 Return str
If str.Length > 4
Return str[0..4]
End If

While str.Length < 4
str = str + " "
WEnd
Return str
End Function

Function WriteUnsignedInt32(stream:TStream, l:Long)
Local p:Int Ptr = Int Ptr VarPtr l

?LittleEndian
stream.WriteInt(p[0])
?BigEndian
stream.WriteInt(p[1])
?

End Function

Function ReadUnsignedInt32:Long(stream:TStream)
Local i:Int = stream.ReadInt()
Local l:Long
Local p:Int Ptr = Int Ptr VarPtr l
?LittleEndian
p[0] = i
?BigEndian
p[1] = i
?
Return l
End Function


Comments : none...