Writing AIFF files

Anyone here have experience of writing binary files in lua? I am trying to export sample buffer data to an AIFF file using lua.

I have been poking around the File Imports Tool (http://www.renoise.com/tools/additional-file-format-import-support) which actually has a AIFF export function but I can’t figure out sending Sample Data into it.

Any takers?

Why you want to support AIFF, isn’t it a purely outdated container format nowadays? For example, there is M4A.

Why you want to support AIFF, isn’t it a purely outdated container format nowadays? For example, there is M4A.

For a very specific use case, I have an OP-1 and it only works with the AIFF format + custom header with sample slice points etc.

I want to make an XRNI to AIFF convertor with slice points & tunings that will load into the OP-1 with these preserved, lets me create sample packs, building blocks that I can jam with in OP-1…

Looks like much of the leg work and techniques have already been cracked by mxb in the file imports toolbut much of this is on stuff I have little knowledge about e.g. binary files, LSB, MSB, 2s complements etc. Could probably figure it all out given enough time, but I’m looking for a shortcut :wink:

Also relevant OP-1 thread here:https://www.operator-1.com/index.php?p=/discussion/comment/8080/

I assume afta8 you mean the ‘generating_aiff’ function? Quickly looking that audiodata parameter seems to want to be a char string of the sample data. I think first you would have to convert normalised -1 +1 renoise sample_buffer data to say 8-bit (or 16-bit) signed sample data into a table, then convert the table to a string for the audiodata parameter.

Thanks 4Tey, that helps, I presume ‘signed sample data’ is the same as PCM data?

Not sure how to convert -1 to +1 values into this, but will research it… Also I presume Renoise is doing this anyway when saving out as wav… maybe there is a shortcut by saving out the Wav file using the API and then stripping the PCM data out of that…

EDIT: Found this http://stackoverflow.com/questions/15087668/how-to-convert-pcm-samples-in-byte-array-as-floating-point-numbers-in-the-range?noredirect=1&lq=1

but if I understand correctly it looks like Audio quality is degraded if you use this method. hmm always thought WAV to AIFF conversion was lossless :wacko:

Yes possibly (although don’t quote me on it :D) linear PCM is signed sample data and yes you could recompose/hack a saved wav file sample data from renoise into a aiff file afta8 :slight_smile:

(But looking from a purely scripting point of view…) Note, quickly looking at the file format(s) you posted afta8 you would have to keep in mind that the function mxb shows gives you a basic aiff file. You would have to modify that function to also output a modified COMM chunk and output a APPL chunk into the file to accommodate for the parameters to the OP-1.

You would have to modify that function to also output a modified COMM chunk and output a APPL chunk into the file to accommodate for the parameters to the OP-1.

Yes, you are right in that, I am hoping that once I have figured out how to create a basic AIFF file, I can hack mxb’s code to do the extra header with a bit of trial and error and a hex editor.

I have the samples generated by the OP-1 to compare against so comparing against these I could do it… but then I could be wrong :slight_smile:

Not that you can really go by this afta8 as this is stupid crude, but I managed to get mxb’s function to save out a aiff file with a mono 16-bit 44100Hz sample with:

local data = {}
local nf = renoise.song().selected_instrument:sample(1).sample_buffer.number_of_frames
local dptr = 0
for n=1,nf do
  local SampNumber = renoise.song().selected_instrument:sample(1).sample_buffer:sample_data(1,n)
  if SampNumber < 0 then
    SampNumber = 65536 - (SampNumber * -1) * 32768 
  else
    SampNumber = SampNumber * 32768
  end
  data[dptr] = string.char(bit.band(SampNumber,0xff))
  dptr = dptr + 1
  data[dptr] = string.char(bit.rshift(SampNumber,8))
  dptr = dptr + 1
end

generate_aiff(1,44100,16,table.concat(data))

Wow! amazing!! :drummer:

Thank you so much! I can work with ‘stupid crude’ :slight_smile:

OP-1 only works with mono samples so this should be enough for my needs, nice work!

I see you are doing some bitwise operations there, I don’t really understand these but does your approach lead to any degradation of audio quality?

Nah, afta8 the bitwise operations is just a way of shifting the bits down and masking bits to output the low-byte/high byte of a 16-bit number into two 8-bit chars. Nothing to do with any audio degradation. The lines you would be most interested in are these:

if SampNumber < 0 then
    SampNumber = 65536 - (SampNumber * -1) * 32768 
  else
    SampNumber = SampNumber * 32768
  end

That’s just a silly hack up from me. There is probably a better way of converting a normalised signed -1…+1 floating point sample into a signed 16-bit integer number. If there is any ‘degradation’ it is going to be here. I’m sure afta8 you can come up with a lot better way :slight_smile:

Ah ok, I get it now, so any errors creeping in will be from the Integer to Float conversion. I suppose the simplest way to test this would be to do a null test with a source wav and the exported aiff.

I will test your code out tonight, but either way its enough to get me going on writing the custom headers. Thanks again :slight_smile:

If there is any ‘degradation’ it is going to be here.

After testing that code and doing a null test, it does introduce noise. Not sure how to fix this, I’m guessing some kind of dither… lua uses doubles for everything which is 64bit I think…

The final aiff output is 16bit so the introduced noise is probably down to bit rate conversion I think, don’t really fancy writing a dither algorithm in lua :slight_smile:

Hmm, interesting afta8. Not sure about dithering/noise algos, but I’ve done a little binary compare test myself. I took a simple 16-bit 44100Hz mono sample.

Asked Renoise to produce a wav file from this and extracted the 16-bit linear PCM sample data. I then ran through my above code to produce a aiff file and extracted the PCM linear data from that file. We are talking a sample size of 19810(bytes) / 2 = 9905 16-bit word size samples. Then I binary compared the two files. They are 100% identical. Now I’m not saying that that is conclusive in any way afta8. I suppose at the moment what I could possibly conclude is that if there is a rounding/error factor creeping in at some point, it is very small between that and a Renoise wav file?

I’m not comparing the binary files just testing with a Sine wave and loading them in Renoise (it will import aiffs) and can hear audible noise on the aiff version… see the attached files.

I’m using your code exactly as above, also attached the tool file if you want to have a look (it will add an aiff export option to the tools menu) - are you on mac or windows?

Ah, more interesting afta8 :slight_smile: Yes I can see the ‘noise’ here. I admit it is a little interesting. I tend to use Linux btw (well it had to be one of the two afta8 :D) The first thought I’m thinking here is to convert that ‘sine_wav_original’ file you have there to a aiff file via a 3rd party program. I’m just thinking if the unix program ‘sox’ could do this.

Ha, well mac is sort of linux :D… I used Audacity to convert to aiff and it is much better… it doesn’t null perfectly but is good enough… see attached…

Yeah, quite interesting and I’m learning more about binary stuff so all good overall :slight_smile:

I got sox to give me a aiff file as well, so at the moment I’ve just spent sometime looking at the two binary files produced. Quickly though I don’t think the header file that mxb’s function produces is quite right (well according to sox anyway…) But that isn’t the full problem. In fact the problem looks kinda strange at the moment, but I’ve got to leave it here afta8. But I will have a look tomorrow at this and see if I can find anything more :slight_smile:

Could a cdp process help with converting to .aiff? Like;

Copysfx (copy/convert soundfiles);http://www.ensemble-software.net/CDPDocs/html/cmcrefmn.htm#COPYSFX

or

Housekeep respec (change sample rate, format or properties of a soudfile);http://www.ensemble-software.net/CDPDocs/html/cgrohous.htm#RESPEC

Maybe there are better more suitable processes, but these sprung to mind :slight_smile:

Ah okay I think I’ve worked out what it was afta8 :slight_smile: I’ve attached a modified version for you, it should now produce your sine wave (without the noise! :smiley: ) Basically it was the ‘endiness’ of the 16-bit word was the wrong way around. Also I forgot that lua is 1-based indexed on its tables whereas I was thinking in C terms (0-based arrays), so it was missing the first sample of data :rolleyes: I’ve corrected that. The other thing afta8 is that save_aiff function is at the moment hard wired to only work on instrument 0, but you can modify that with the selected instrument number etc… So give that a go afta8 and see if that is a bit better :slight_smile:

So give that a go afta8 and see if that is a bit better :slight_smile:

Thats perfect, even cleaner than the audacity export! :smiley:

Couple of questions to better my understanding…

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?

Could a cdp process help with converting to .aiff? Like;

Yes and could also use sox, however that is only half of what I need to do, the next bit is to ‘customise’ the meta data in the sample to include slice data and tunings, this is why it is better to try and use mxb’s code as it is already writing metadata and hopefully will be easier to modify.

Anyway 4Tey has worked some magic here to help me on my way :slight_smile: