Reading region data from AIFF/BWAV?

Nov 26, 2008 at 6:50am

Reading region data from AIFF/BWAV?

Hi out there,
Wonder if Max can do this? Can I create some regions in an audio file in BIAS Peak or another editor and have Max read them for eventual use in [coll], [sflist], [sfplay], etc.? I believe AIFFs need a kludge of some sort on the part of the application to read/write region definitions, but is there an agreed-upon way, or can (B)WAV work?

Thanks,
Brian

#41054
Nov 26, 2008 at 6:59am

I have been hoping for something similar as well (along with writing BWAV/other metadata) but I doubt it’s possible without someone interested enough writing an external.

I’ve researched it a bit, reading/writing that stuff seems excruciatingly difficult – though it might be possible with libsoundfile, or using the actual JUCE framework (JUCE does have some built-in functions for reading/writing BWF metadata).

Sadly, the region metadata is, I believe, a proprietary digidesign chunk – I don’t know if BIAS uses the same format as Pro Tools but it seems unlikely.

Glad there’s at least one other person that wants to do things with metadata in Max, though.

#145808
Nov 26, 2008 at 8:22am

Hi,
Yeah, glad to see I’m the only one wondering where this is. I assume you mean *AIFF* metadata is proprietary? I would think the BWAV stuff would be more standard & accessible, but I recall a support where even 2 very popular apps (Pro Tools & Peak) were not being able to get along about where to put it.

Kind of surprising there isn’t anything out there to do it in Max, since this is the one of the basic building-blocks of editing.

But I’m looking (and not capable of writing an external at this point) if someone can point us to it or has some insight in the issues involved to share.

Thanks,
Brian

#145809
Nov 26, 2008 at 8:48am

I haven’t looked much into AIFF metadata, actually, though the format is actually based on the same standard as .wav files:

IFF is the base format (I think it’s “Interchange File Format”). .aif is an ‘AIFF’ – audio or Apple interchange file format, and .wav is ‘RIFF’ – resource interchange file format. RIFF is a microsoft format and is actually the same basic file format used for .avi’s as well.

Wikipedia can explain it better than I probably can:

http://en.wikipedia.org/wiki/Interchange_File_Format

http://en.wikipedia.org/wiki/Resource_Interchange_File_Format

http://en.wikipedia.org/wiki/AIFF

Anyway, each one is comprised of ‘chunks’ which contain various bits of information. Each chunk starts with a four character code defining what the chunk contains – for instance RIFF has a ‘fmt ‘ chunk defining encoding and whatnot, while the actual audio data is stored in a ‘data’ chunk.

Pro Tools stores its region definitions in a proprietary chunk (IE there is no documentation for it, though I’ve found that people who’ve figured it out are willing to share). This chunk is labelled ‘regn’ and contains things like the region name, where the region starts and stops, etc. But it’s much much more complex than that.

Really the only major difference between AIFF and RIFF is endianness. (see http://www.cs.umass.edu/~Verts/cs32/endian.html )

I’ve been research this stuff for about two weeks now, and it’s surprisingly complex to try and write an audio file from scratch. BWAV metadata is slightly better documented but you’ll be hard pressed to find any sample code that just says ‘Here’s how to write some BWAV data to a wav file’.

One plus to the way IFF files deal with chunking, though – a well written app will ignore chunks it doesn’t understand, and many chunks (but not all) don’t have to be in any particular order — most programs are perfectly happy with having a BWAV (properly known as ‘bext’) chunk come after the audio data – less of a header and more of a footer.

#145810
Nov 26, 2008 at 11:12am

On 26 nov. 08, at 07:50, Brian Heller wrote:

>
> Hi out there,
> Wonder if Max can do this? Can I create some regions in an audio
> file in BIAS Peak or another editor and have Max read them for
> eventual use in [coll], [sflist], [sfplay], etc.?

You should have a look at my [sfmarkers~] external. Mac only, and for
markers only (although I could add regions in the future).

-> http://www.crfmw.be/max
_____________________________
Patrick Delges

Centre de Recherches et de Formation Musicales de Wallonie asbl

http://www.crfmw.be/max

#145811
Nov 26, 2008 at 11:31am

On 26 nov. 08, at 09:48, mushoo wrote:

> Really the only major difference between AIFF and RIFF is
> endianness. (see http://www.cs.umass.edu/~Verts/cs32/endian.html )

I’m afraid there are no chunks encoded the same way in AIFF and in
WAVE, it’s unfortunately not only a question of endianess. And WAVE is
unsurprisingly very badly documented.

> I’ve been research this stuff for about two weeks now, and it’s
> surprisingly complex to try and write an audio file from scratch.
> BWAV metadata is slightly better documented but you’ll be hard
> pressed to find any sample code that just says ‘Here’s how to write
> some BWAV data to a wav file’.

The EBU specs explains very precisely the structure of the bext chunk.

p

_____________________________
Patrick Delges

Centre de Recherches et de Formation Musicales de Wallonie asbl

http://www.crfmw.be/max

#145812
Nov 26, 2008 at 4:22pm

Quote: Patrick Delges wrote on Wed, 26 November 2008 04:31
—————————————————-
>
> On 26 nov. 08, at 09:48, mushoo wrote:
>
> > Really the only major difference between AIFF and RIFF is
> > endianness. (see http://www.cs.umass.edu/~Verts/cs32/endian.html )
>
> I’m afraid there are no chunks encoded the same way in AIFF and in
> WAVE, it’s unfortunately not only a question of endianess. And WAVE is
> unsurprisingly very badly documented.

I’m not particularly surprised that the chunks are all different – I hadn’t looked at AIFF much at all.

> > I’ve been research this stuff for about two weeks now, and it’s
> > surprisingly complex to try and write an audio file from scratch.
> > BWAV metadata is slightly better documented but you’ll be hard
> > pressed to find any sample code that just says ‘Here’s how to write
> > some BWAV data to a wav file’.
>
> The EBU specs explains very precisely the structure of the bext chunk.

True but overall I’m still confused about writing/reading chunks in general.

>
> p
>
> _____________________________
> Patrick Delges
>
> Centre de Recherches et de Formation Musicales de Wallonie asbl
> http://www.crfmw.be/max
>
>
—————————————————-

#145813
Nov 27, 2008 at 8:55am

On 26 nov. 08, at 17:22, mushoo wrote:

>> The EBU specs explains very precisely the structure of the bext
>> chunk.
>
> True but overall I’m still confused about writing/reading chunks in
> general.

That’s another problem.
My [sfmarkers~] is not open source (the C code is too disgusting and
badly commented), but here is some JavaScript I coded a couple of
years ago. It may give you some hints. I stopped using Js as soon as I
noticed how slow it was to deal with file i/o, so this code is jut a
test and is probably very buggy…

###save as addmarker.js

// sfmarkers goes JS
// pdelges@radiantslab.com
// users.skynet.be/crfmw/max

// 2005

autowatch = 1;

var theSourceFile;
var theDestinationFile;

var theMarkers;
var lastMarkerID = -1 ; // -1 means there is no marker

function bang()

{
display();
}

function read (filename)

{

theMarkers = new Array; // fresh array

var chunkTag = new Array (4);
var chunkSize = new Array (4);

theSourceFile = new File (filename, “read”, “AIFF”);

post (“nFile:”,theSourceFile.filename, theSourceFile.isopen, “n”);

theSourceFile.position += 12; // skip start of header, should check
file type

if (searchMarkerChunk (theSourceFile))
{
readMarkerChunk (theSourceFile);
// displayMarkers ();
}
else
post (“nopen”);

theSourceFile.close ();
}

searchMarkerChunk.local = 1;
function searchMarkerChunk (theSourceFile)

{
var chunkTag = new Array (4);
var chunkSize = new Array (4);

var chunkTagString = new String ;
var chunckSizeInBytes = 0;

var theEof = theSourceFile.eof;

do { // let’s jump from chunk to chunk

// theSourceFile.position += parseInt(chunckSizeInBytes) ;

chunkTag = theSourceFile.readbytes (4);
chunkSize = theSourceFile.readbytes (4);

chunckSizeInBytes = add4Bytes(chunkSize);

chunkTagString = String.fromCharCode
(chunkTag[0],chunkTag[1],chunkTag[2],chunkTag[3]);

post (“Chunk”, chunkTagString, “Size”, chunckSizeInBytes,”pos:”,
theSourceFile.position, “n”);

}
while (chunkTagString != “MARK” && (theSourceFile.position +=
parseInt(chunckSizeInBytes)) < theEof);

return (chunkTagString == “MARK”);
}

readMarkerChunk.local = 1;
function readMarkerChunk (theSourceFile)

{
var numberOfMarkers;

lastMarkerID = 0;

numberOfMarkers = theSourceFile.readint16 (1);
post (“number of markers:”, numberOfMarkers, “n”);

for (var i =0; i < numberOfMarkers; i++)
{
var aMarker = new Object;

aMarker.id = theSourceFile.readint16 (1);
lastMarkerID = Math.max (lastMarkerID, aMarker.id);

aMarker.position = theSourceFile.readint32 (1);
aMarker.nameLength = parseInt(theSourceFile.readbytes (1));
aMarker.name = (theSourceFile.readchars
(aMarker.nameLength)).join(“”);

if (!(aMarker.nameLength % 2)) // beware useless “space padding” in
Peak
theSourceFile.position++;

theMarkers.push (aMarker);
}
post (“lastID”, lastMarkerID, “n”);

}

function display ()

{
for (var i=0; i < theMarkers.length; i++)
{
var aMarker = theMarkers[i];
for (var j in aMarker)
post (j, aMarker[j], “-”);
post (“n”);

}
}

function addMarker (name, position)

{
var newMarker = new Object;

newMarker.id = ++lastMarkerID;
newMarker.position = position;
newMarker.nameLength = name.length;
newMarker.name = name;

theMarkers.push (newMarker);

}

function saveFile (newFilename)

{
theSourceFile.open (); // open the last opened file
theDestinationFile = new File (newFilename, “write”, “AIFF”);

if (!theDestinationFile.isopen)
{
post (“Cannot create file”, newFilename, “n”);
return;
}

theDestinationFile.writestring (“FORM”);

var totalSizePostion = theDestinationFile.position;
theDestinationFile.writeint32 (0); // to be updated later

theDestinationFile.writestring (“AIFF”);

// now, let’s copy the chunks.

var chunkTag = new Array (4);
var chunkSize = new Array (4);

var chunkTagString = new String ;
var chunckSizeInBytes = 0;

var theEof = theSourceFile.eof;

theSourceFile.position = 12; // go back at start…

do
{ // let’s jump from chunk to chunk

chunkTag = theSourceFile.readbytes (4);
chunkSize = theSourceFile.readbytes (4);

chunckSizeInBytes = add4Bytes(chunkSize);
chunkTagString = String.fromCharCode
(chunkTag[0],chunkTag[1],chunkTag[2],chunkTag[3]);

if (chunkTagString == “MARK”)
theSourceFile.position += parseInt(chunckSizeInBytes); // jump at
the end of the chunk

else { if (chunkTagString == “COMM”)
{
copyChunk (“COMM”, chunckSizeInBytes); // first we copy COMM
post (“COMM “, chunckSizeInBytes, ” “, theSourceFile.position, “.”);
writeNewMarkerChunk (); // then MARK
}

else { if (chunkTagString != “MARK”) // MARK is already done
{
copyChunk (chunkTagString, chunckSizeInBytes);
post (chunkTagString, chunckSizeInBytes, ” “,
theSourceFile.position, “.”);
}
}
}

}
while (theSourceFile.position < theEof);

// update size of FORM

theDestinationFile.position = 4;
theDestinationFile.writeint32 (theDestinationFile.eof – 8);

theDestinationFile.close();
theSourceFile.close();
}

writeNewMarkerChunk.local = 1;
function writeNewMarkerChunk ()

{

var initialPositionInFile = theDestinationFile.position;

theDestinationFile.writestring (“MARK”);
theDestinationFile.writeint32 (0); // will be changed later
theDestinationFile.writeint16 (theMarkers.length);

for (var i=0; i < theMarkers.length; i++)
{
theDestinationFile.writeint16 (theMarkers[i].id);
theDestinationFile.writeint32 (theMarkers[i].position);
theDestinationFile.writebytes (theMarkers[i].nameLength);
theDestinationFile.writestring (theMarkers[i].name);

if (theDestinationFile.position % 2)
theDestinationFile.writebytes (0); // padding
}

// write size of chunk

var endPosition = theDestinationFile.position;
var chunkSize = endPosition – initialPositionInFile;

theDestinationFile.position = initialPositionInFile + 4; // jump back
theDestinationFile.writeint32 (chunkSize – 8);

theDestinationFile.position = endPosition; // jump to end of chunk
}

copyChunk.local = 1;
function copyChunk(tag,size)

{
var i;
var buffer;

theDestinationFile.writestring (tag);
theDestinationFile.writeint32 (size);

if (size < 32)
theDestinationFile.writebytes (theSourceFile.readbytes (size));

else
for (i=0;i
{
buffer = theSourceFile.readbytes (32);

if (buffer.length)
{
i += buffer.length;
theDestinationFile.writebytes (buffer);
}
else
{
post (“weird!n”);
break;
}
}
}

add4Bytes.local = 1;
function add4Bytes (anArray)

{
var sum = 0;

for (i=0,j=3; i<4; i++,j--) {
sum += anArray[i] * Math.pow(256, j);
// post (sum, anArray[i], Math.pow(256, j),”n”); // remove later!
}

return sum.toFixed(0); // huge difference with float !!!

}

#### max patch

max v2;
#N vpatcher 683 526 1224 836;
#P window setfont “Sans Serif” 9.;
#P newex 165 273 31 196617 print;
#P number 243 269 35 9 0 0 0 3 0 0 0 221 221 221 222 222 222 0 0 0;
#P message 180 225 29 196617 open;
#P newex 185 246 62 196617 sfmarkers~;
#P message 81 68 313 196617 saveFile ti_HD_X:/Users/pdelges/Projects/
sfmarkers+~/tmp.aiff;
#P message 53 129 105 196617 addMarker acso 6666;
#P message 28 111 79 196617 displayMarkers;
#P message 83 162 83 196617 read nothing.aiff;
#P newex 19 37 99 196617 bgcolor 255 227 68;
#P message 99 204 294 196617 read ti_HD_X:/Users/pdelges/Projects/
sfmarkers+~/file.aiff;
#P message 211 161 310 196617 ti_HD_X:/Users/pdelges/Projects/sfmarkers
+~/file.aiff;
#P message 93 184 65 196617 read file.aiff;
#P newex 59 244 69 196617 js addmarker;
#P connect 3 0 0 0;
#P connect 1 0 0 0;
#P connect 5 0 0 0;
#P connect 6 0 0 0;
#P connect 7 0 0 0;
#P connect 8 0 0 0;
#P connect 9 0 12 0;
#P connect 10 0 9 0;
#P connect 9 1 11 0;
#P pop;

_____________________________
Patrick Delges

Centre de Recherches et de Formation Musicales de Wallonie asbl

http://www.crfmw.be/max

#145814
Nov 27, 2008 at 9:44pm

I’m too tired to read the whole thing right now, and I’m trying to avoid thinking about this project for the weekend, but I can tell you this:

That code will be fantastically useful, and thank you. Just having something that describes the broad strokes to the process is awesome. (I’ve never dealt with Js, but I’m sure it will make sense, seems very readable, well commented.)

#145815

You must be logged in to reply to this topic.