Writing AIFF files

At this link http://stackoverflow.com/questions/15087668/how-to-convert-pcm-samples-in-byte-array-as-floating-point-numbers-in-the-range?noredirect=1&lq=1itit says the " range of 16 bit integers is -32768 to 32767" however in your code you are always multiplying by 32768 and the max range looks like it is65536 (should this be 65535?). Are you taking a different approach here or am I missing something? I notice when I do a null test it zero’s completely except for a small section where the sine wave reaches it’s peak value.

Also you mention ‘endiness’ and on the wikipedia page for aiff https://en.wikipedia.org/wiki/Audio_Interchange_File_Format itsays “With the development of theMac OS Xoperating system, Apple created a new type of AIFF which is, in effect, an alternativelittle-endianbyte order format.”- Therefore is your code here creating a 'little-endian" format of the aiff?

Yep, that’s fair questions afta8. About the multiplier, yes it probably is 65535 rather than 65536, so if it produces better PCM values around zero then by all means change that, no trouble. I just quickly guessed, not much thought applied on my part there afta8 :smiley:

Now the ‘endiness’ business… At the moment the code produces a big endian PCM data stream. Old style Amiga/Apple probably favoured this as they were Motorola 680x0 based machines, where the processor architecture was big endian. However I think what wiki is saying is that because Apple moved to Intel x32/x64 processors which are little endian in nature, Apple changed the AIFF file format to accommodate this. Now your OP-1? Does it want little or big endian stream? I don’t own a OP-1 afta8 but with the file I have here it seems to me to want a little endian PCM stream. Luckily it shouldn’t be a problem because all you do is to swap this:

--This is big endian...
data[dptr] = string.char(bit.band(bit.rshift(SampNumber,8),0xff))
dptr = dptr + 1
data[dptr] = string.char(bit.band(SampNumber,0xff))
dptr = dptr + 1

To this:

--This is little endian...
data[dptr] = string.char(bit.band(SampNumber,0xff))
dptr = dptr + 1
data[dptr] = string.char(bit.band(bit.rshift(SampNumber,8),0xff))
dptr = dptr + 1

But you would have to try that when you get to the stage of uploading a sample to your OP-1 :slight_smile:

I’m not an expert on the AIFF file format, but I assume there is something in the header that tells it what ‘endiness’ the stream is in afta8.

[Edit: Yes there is, I can see it here in this file :slight_smile: The other thing to mention is that when you do start to modify the header afta8, I don’t think Renoise can load any other format of AIFF other than standard ‘old style’ big endian format. So you would probably have to use another program to load those AIFF files. Ah, hello Djeroek :)]

Great info thanks for sharing.

For some reason I love to read such bit shifting operations/code, I dunno, gives me instant nostalgic feelings :slight_smile:

Thanks for that @4tey

Thanks 4Tey, some useful knowledge there :slight_smile:

Now your OP-1? Does it want little or big endian stream? I don’t own a OP-1 afta8 but with the file I have here it seems to me to want a little endian PCM stream.

It looks like it will accept either, I loaded the sample created by your code (big endian) into the OP-1 and it works fine. If I look at the samples generated in the OP-1 in a hex editor you can see: “sowt)Signed integer (little-endian)” so I presume the OP-1 native format is little endian.

I also generated a sample for the OP-1 using a standalone software tool for it: http://now.teenageengineering.com/post/47348903156/op-1-drum-utility and I think that is big endian, but not sure how to tell when looking at it in a Hex editor

So overall I don’t think the OP-1 is fussed about the “endiness”

The other thing to mention is that when you do start to modify the header afta8, I don’t think Renoise can load any other format of AIFF other than standard ‘old style’ big endian format.

It works fine for me, I tried loading the native sample generated on the OP-1 which is little endian and it loads into Renoise fine.

If you are interested I have attached 3 different versions of same sample:

  1. lua_export.aiff - The file generated by your code

  2. xfer_util.aif - The same file exported by the OP1 utility I mentioned earlier

  3. op1_native - The same sample re-saved within the OP1

You can see in the 2 and 3 the following meta data chunk which I now need to put into the lua export file in a APPL chunk:

op-1{"drum_version":1,"type":"drum","name":"user","octave":0,"pitch":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"start":[0,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246,75324246],"end":[75320188,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960,117226960],"playmode":[8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192],"reverse":[8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192],"volume":[8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192,8192],"dyna_env":[0,8192,0,8192,0,0,0,0],"fx_active":false,"fx_type":"delay","fx_params":[8000,8000,8000,8000,8000,8000,8000,8000],"lfo_active":false,"lfo_type":"tremolo","lfo_params":[16000,16000,16000,16000,0,0,0,0]}

Should be fairly straightforward right :wink:

It looks like it will accept either, I loaded the sample created by your code (big endian) into the OP-1 and it works fine. If I look at the samples generated in the OP-1 in a hex editor you can see: “sowt)Signed integer (little-endian)” so I presume the OP-1 native format is little endian.

Smashing afta8, your OP-1 doesn’t mind which way around the bytes are. The file ‘op1_native’ is little endian. The other two files are big endian. Basically when it hasn’t got ‘sowt’ in the header chances are it is going to be a standard big endian :slight_smile:

It works fine for me, I tried loading the native sample generated on the OP-1 which is little endian and it loads into Renoise fine.

Yeah, kinda makes sense that you can on Mac OSX as Taktik is probably running through and finding a decoder (Quicktime?) I can’t load AIFF little endian files afta8 as I don’t have the decoder in Linux :slight_smile:

Should be fairly straightforward right :wink:

Well, err, yes?..err maybe :smiley: Bit of programming, got to format your string and get the length :slight_smile:

There is afta8 just something tiny tiny I have just noticed with those 3 files…But I’m just going to have another look…

Yeah, kinda tricky to explain afta8, but I shall try with this:

Attachment 7212 not found.

The top line is a partial hex dump of our ‘lua_export.aiff’ SSND chunk. The bottom line is ‘xfer_util.aiff’ SSND chunk. See the 3 differences? Near the end of both lines we have sample 1 and sample 2 reading FB 8A (sample 1) and 01 2E (sample 2) in the other file FB 8A and 01 2D. But I think we know that that is to do with my hacky/dodgy calculation afta8 that you will eventually correct :wink: Anyway I’m not concerned with that so much at all, it’s the other two.

Looking near the beginning of each line we have 00 00 E1 C6 and 00 00 E1 BC. That is the length of the chunk. So in this case 0xE1C6 = 57798 bytes and 0xE1BC = 57788 bytes. That’s a difference of 10 bytes. The final difference is there is an extra 4 zeros in the xfer file before you get to sample 1. Don’t know afta8 about that, the length bothers me and those 4 zeros :slight_smile:

I think this is something to do with how xfer_util processes the sounds, it looks like it is inserting 2 samples of silence at the start of the sample, if you open both in Renoise you can see that lua_export.aiff is 28889 samples long and xfer_util.aif is 28891 samples long, if you zoom into the beginning of the samples you can see 2 sample frames of silence. My guess is that is something to do with how the xfer util prepares the samples rather than any conversion code.

So I have this sort of working, well in a specific test case version. See attached, if you use this version of the tool and the attached wav file the output will match the earlier, “xfer_util.aif” I posted. It also works when imported to OP1, slice position preserved etc…

Couple of things I need to figure out though.

If you look in a hex editor at the output file, there is a section after the APPL, see image:

7214 Screen Shot 2017-01-10 at 22.14.26.png

At the moment I am just copying this from the original file to make it work, but I need to work out how to calculate this properly, if you look at this link:http://paulbourke.net/dataformats/audio/

Under the section on Application Specific Chunk it says:

Application Specific Chunk

The Application Specific Chunk can be used for any purposes whatsoever by manufacturers of applications. For example, an application that edits sounds might want to use this chunk to store editor state parameters such as magnification levels, last cursor position, and the like.

#define ApplicationSpecificID ‘APPL’ /* ckID for Application /
/
Specific Chunk. */
typedef struct {
ID ckID;
long ckSize;
OSType applicationSignature;
char data;
} ApplicationSpecificChunk;

_ckID_is always ‘APPL’.ckSize_is the size of the data portion of the chunk, in bytes. It does not include the 8 bytes used by_ckID_and_ckSize.

So I guess these 8 digits represent the size of the data in bytes, but how to work that out?

Also at the end of the data portion there is 6 digits:

7215 Screen Shot 2017-01-10 at 22.15.13.png

If you take these out the file doesn’t load but I’m not sure these are for?

Finally I need to work out how the sample frame position translates to the numbers in the data format, for example the start position in the data format is given as:75324246, however the sample frame number is 18564, not sure how you go from one to the other…

Other than all that, I’m nearly there :smiley:

Hmm afta8, I wonder why that xfer util inserts two samples of silence at the beginning? I assume it would also have to adjust any loop points etc?.. The length calculation still bothers me a touch…anyway… :slight_smile:

As I far as I can see afta8 the 4 length bytes following the APPL string is just the length of the string:

Attachment 7218 not found.

In the file ‘op1_native’ above I’ve highlighted the APPL string, it is 0x4AE (1198) bytes long. I also see that this particular string is terminated with 0x0A (just a unix end of line terminator?) Of course what you’ve eventually got to do afta8 is to ‘pre build’ your string (with all your parameters from your tool etc…), calculate the length (including any line termination), then insert that info into the file APPL chunk.

Don’t know about your sample start position as this is to do with the OP-1 afta8. But I understand what you mean, could be tricky :slight_smile:

Edit:

The other thing to mention/consider (not more things :rolleyes: ) afta8 in mxb’s aiff function is line 30 (from the file you attached):

30 f:write(hex_pack(string.format("%08X", length))) -- chunk size = file length - 8

Where mxb computes the overall file length. To be more proper that also would have to be adjusted to accommodate your APPL chunk size. But I suppose it also depends on how lenient the OP-1 import is :slight_smile:

Those two samples at the beginning are bothering me also, however seems to work ok for now, I’ll leave those on the list of things to come back to.

The length thing makes sense now, so I just need to have one long string and calculate it’s size in bytes. I also realised that the OP1 data is in JSON format so I can just use a LUA/JSON library which should make this bit easier.

The overall file length I hadn’t thought about but it has not been an issue so far, will see how far I get without touching it :slight_smile:

I was also searching through Github and found a couple of projects where this mission has already been tackled.

https://github.com/padenot/libop1

https://github.com/operator1/op1/blob/master/op1/src/main/java/com/op1/util/DrumkitBuilder.java

This will maybe help with some of these issues I have found, I already figured that the sample slice points are based on the bytes in the sample data.

I found just typing .aif at the end of the file once created works.