How do I read the samplerate of an aiff?

DSP, Plug-in and Host development discussion.
KVRist
65 posts since 24 Jun, 2005 from Berlin, Germany

Post Thu Apr 22, 2021 3:02 pm

Apparently the samplerate in AIFF is written as 80-bit floating point /extended / long double.

How do I read it in C++ Mac/Win?
before time there was no time

User avatar
KVRian
786 posts since 25 Sep, 2014 from Specific Northwest

Post Thu Apr 22, 2021 5:24 pm

Convert it to WAV format and then open it? :lol:

The 80-bit float doesn't sound right to me. AIFF uses the same chunking scheme as WAV files, but with a few differences. WAV uses a four-byte integer to hold the sample rate as part of the "fmt " chunk.

It looks like AIFF uses a "comm" chunk instead. I'm not sure how long of an integer an "extended" type is, though.

Code: Select all

4.0  COMMON CHUNK

The Common Chunk describes fundamental parameters of the sampled sound.

#define CommonID 'COMM' /* ckID for Common Chunk */
typedef struct {
  ID ckID; /*  'COMM'  */
  long kDataSize;
  short numChannels; /* # audio channels */
  unsigned long numSampleFrames; /* # sample frames = samples/channel */
  short sampleSize; /* # bits/sample */
  extended sampleRate; /* sample_frames/sec */
  ID compressionType; /* compression type ID code */
  pstring compressionName; /* human-readable compression type name */
} CommonChunk;
ckID is always 'COMM'.

KVRAF
6316 posts since 12 Feb, 2006 from Helsinki, Finland

Post Thu Apr 22, 2021 7:19 pm

syntonica wrote:
Thu Apr 22, 2021 5:24 pm
I'm not sure how long of an integer an "extended" type is, though.
It is not an integer.

According to the specification: "80 bit IEEE Standard 754 floating point number (Standard Apple Numeric Environment [SANE] data type Extended)."

The x87 FPU uses these internally, but how they are stored in the file (or how to get them from the file to the FPU so you can then convert to something easier to work with), I have no clue.
Preferred pronouns would be "it/it" because according to this country, I'm a piece of human trash.

User avatar
KVRian
786 posts since 25 Sep, 2014 from Specific Northwest

Post Thu Apr 22, 2021 8:22 pm

Crap. You're right, it's right there defined in the AIFF standard. I guess it helps to read the fore matter. But it really makes zero sense to have a fractional sample rate, to me. Even a four-byte fixed point would have better if you really needed it. Weird.

Anyways, this programmer was kind enough to write a utility:

https://github.com/brianmhunt/float80

I still stand by converting them all to WAV. AIFFs never took off. :lol:

User avatar
KVRist
222 posts since 24 Aug, 2014 from Moscow

Post Fri Apr 23, 2021 12:42 am

timefreezer wrote:
Thu Apr 22, 2021 3:02 pm
How do I read it in C++ Mac/Win?
The same way you read other floats. Just pick appropriate type: https://en.wikipedia.org/wiki/Extended_ ... ge_support

KVRist

Topic Starter

65 posts since 24 Jun, 2005 from Berlin, Germany

Post Fri Apr 23, 2021 2:51 am

__float80 is an unknown type.
before time there was no time

User avatar
KVRist
222 posts since 24 Aug, 2014 from Moscow

Post Fri Apr 23, 2021 4:32 am

timefreezer wrote:
Fri Apr 23, 2021 2:51 am
__float80 is an unknown type.
Did you try

Code: Select all

long double
?

KVRist

Topic Starter

65 posts since 24 Jun, 2005 from Berlin, Germany

Post Fri Apr 23, 2021 6:19 am

long double test;
fpos=fmt_pos+16; fsetpos(fileHandle, &fpos);
fread(&test, sizeof(long double), 1, fileHandle);

no right result
before time there was no time

KVRist

Topic Starter

65 posts since 24 Jun, 2005 from Berlin, Germany

Post Fri Apr 23, 2021 6:48 am

long double is not the right type.

C++, Xcode
long double test=44100;
is coded as 00000000 000044AC 0E40527A FF7F0000

in aiff it is coded as 400EAC44 00000000 00004348 414E0000

There are some endian and other problems.
before time there was no time

User avatar
KVRian
772 posts since 25 Apr, 2011

Post Fri Apr 23, 2021 8:39 am

If you're expecting to convert to an integer samplerate, I reckon you can just use the first 48 bits of the 80-bit number and do something like (*warning, untested pseudocode)

Code: Select all

exp=first 16 bits // (=0x400e)
exp=exp-0x3fff // (=0xf)
man=bits 16..47 // (=0xac440000)
rate=man>>(0x1f-exp) // (= man>>0x10 =0xac44 =44100dec)
It might be an idea to round up 'man' by 1 if bit 48 is set in the original number.
This should handle a pretty wide range of values.

User avatar
KVRist
222 posts since 24 Aug, 2014 from Moscow

Post Fri Apr 23, 2021 9:10 am

Try this:

Code: Select all

#include <cstdio>
#include <cstdint>

double parse_float80be(uint8_t* data) {
  double sign = ((data[0] >> 7) & 1) ? -1 : 1;
  uint16_t exponent = (uint16_t(data[0] & 0x7F) << 8) | data[1];

  double base = ((data[2] >> 7) & 1) ? 1 : 0;
  uint64_t mantissa = data[2] & 0x7F;
  for (int i = 1; i < 8; ++i) {
    mantissa = (mantissa << 8)|data[2 + i];
  }

  //value = (-1) ^ s * (base + m / 2 ^ 63) * 2 ^ (e - 16383)
  return sign * (base + double(mantissa)/(uint64_t(1) << 63)) * exp2(double(int32_t(exponent - 16383)));
}

double read_extended(FILE* f) {
  uint8_t data[10];
  fread(&data, sizeof(data), 1, f);
  return parse_float80be(data);
}

KVRist

Topic Starter

65 posts since 24 Jun, 2005 from Berlin, Germany

Post Sat Apr 24, 2021 2:54 am

Hi kryptonaut and Vokbuz,

I can confirm that I have tested both of your methods between 6kHz and 192 kHz and both seem to work.

This is how I implemented the kryptonaut-version, I like it because it is really short:

uint32_t exp= 256*TempByte[0]+TempByte[1]; //first 16 bits
exp=exp-0x3fff;
uint32_t man = 16777216 * TempByte[2] + 65536 * TempByte[3] + 256 * TempByte[4] + TempByte[5]; //bits 16..47
uint32_t rate= (uint32_t) (man >> (0x1f-exp));

Thank you guys!
before time there was no time

User avatar
KVRist
222 posts since 24 Aug, 2014 from Moscow

Post Sun Apr 25, 2021 1:17 am

I don't think it is a good idea to round value that potentially may have fraction.

KVRist

Topic Starter

65 posts since 24 Jun, 2005 from Berlin, Germany

Post Sun Apr 25, 2021 1:40 am

The AIFF-Samplerate-Format is made for a great variety of numbers, including MHz, and non integer values. We do not need them for music. The main task is to distingwish between 44100, 48000, 88200, ... 192000 Hz. 18 bit integer is enough for us.
before time there was no time

User avatar
KVRian
772 posts since 25 Apr, 2011

Post Sun Apr 25, 2021 11:49 pm

Glad you found the integer code useful - if you want to round rather than truncate fractions you could replace the last line of your code with something like

Code: Select all

uint32_t rate= (uint32_t) (man >> (0x1e-exp));
t_rate+=1;
t_rate>>=1;
which ought to round fractional numbers a bit better.
Note that it assumes that exp<0x1f. Also the code makes no effort to handle negative numbers - it's a bit of a fudge that works for integers in the expected range. :)

Return to “DSP and Plug-in Development”