I am writing a program that reads the contents of an .aiff audio file. I have gotten as far as reading all of the meta data up to the left and right audio channels with one exception. The sample rate, which for a CD is 44,100, is represented as an 80 bit IEEE Standard 754 floating point number. I can read the 10 bytes into a variable but cannot figure out how to decode/convert it to a rust type such as f64. It is my understanding, and I could be wrong, there is a sign (1 bit) a fractional part (63 bits) and an exponent (15 bits) but totals 1 bit shy of 80. There is also a bias of 16383 which is subtracted from the exponent. I just do not know how to even start. Any assistance will be appreciated. Below is a part of my function and the println! output for the entire function.
fn read_aiff(file_path: &str) -> io::Result<()> {
let mut file = File::open(file_path)?;
let mut buffer2 = [0; 2];
let mut buffer4 = [0; 4];
let mut buffer10 = [0; 10];
.....
let _ = file.read_exact(&mut buffer10);
println!("sample rate = {:?}", buffer10);
text FORM = "FORM"
form size = 9389783
text AIFF = "AIFF"
text COMM = "COMM"
chunk size = 18
num channels = 2
number of sample frames = 2319872
sample size = 16
sample rate = [64, 14, 172, 68, 0, 0, 0, 0, 0, 0]
text SSND = "SSND"
chunk size = 9279496
offset = 0
block size = 0
Note that it's not in the https://en.wikipedia.org/wiki/IEEE_754#Basic_and_interchange_formats list, so personally I probably wouldn't call it "an 80 bit IEEE Standard 754 floating point number."
I guess it's probably the x87 extension? https://en.wikipedia.org/wiki/Extended_precision#x86_extended-precision_format suggests that's 64 bits of mantissa.
But you should probably find some data where you know the expected decodings and test against them. Who knows what it's doing.
FWIW, that's how the AIFF spec here defines its extended data type, "80 bit IEEE Standard 754 floating point number (Standard Apple Numeric Environment [SANE] data type Extended)."
It also says, "All data is stored in Motorola 68000 format," so it might be the one where Wikipedia says:
(similar to the Intel format, although padded to a 96-bit format with 16 unused bits inserted between the exponent and significand fields, and values with exponent zero and bit 63 one are normalized values)
It might be easier to find another AIFF library and see how they deal with this.
I don't know in what format .aiff files stores the date. there's an extended crate on crates.io, it's very small, give it a try:
play with the endianness and see if the value looks correct.
There's a SANE manual here that indicates the 80-bit format has an explicit 1 bit preceding the fractional part (semantic page 18, PDF page 42; key is two pages prior). No guarantees from me that's actually what you need
.
You can see the number 44,100 in big endian here. It's 256 * 172 + 68. You can also see the biased exponent. The first two bytes give you 256 * 64 + 14 = 16398. The most significant bit is zero (it's positive). When you remove the bias you get an exponent of 15. This format unlike f64 includes an explicit leading 1 in the mantissa. Anyway, to convert to a float, you take the last 8 bytes and convert to u64 with from_be_bytes. This is the mantissa. Take the first two bytes and convert to u16 with from_be_bytes. Remove the sign bit and call the remaining thing the exponent. Roughly the number you want is mantissa * 2^(exponent - 16383 - 63), not including the sign. If you're a stickler for details there's all sorts of stuff you need to consider (what if the exponent is too big? How do you round the bits of the mantissa that don't fit in f64?) But you're not going to encounter these kinds of numbers in your application.
Thank you. I got the function below to work but after reading your post I think I went the long way round the barn. I understand the multiplication by 256. And I follow the mantissa, exponent and subtraction of the bias but not the -63. Is this what eliminates the meta data at bit 63?
fn f80_to_f64_be(ten_bytes: [u8; 10]) -> f64 {
let sign_exponent = u16::from_be_bytes([ten_bytes[0], ten_bytes[1]]);
let sign = (sign_exponent >> 15) as u64;
let exponent_f80 = (sign_exponent & 0x7FFF) as i32;
let mut mantissa_f80 = u64::from_be_bytes([
ten_bytes[2], ten_bytes[3], ten_bytes[4], ten_bytes[5],
ten_bytes[6], ten_bytes[7], ten_bytes[8], ten_bytes[9]
]);
let unbiased_exp = exponent_f80 - 16383;
let exponent_f64 = (unbiased_exp + 1023) as u64;
mantissa_f80 &= !(1 << 63);
let mantissa_f64 = mantissa_f80 >> 11;
let f64_bytes = (sign << 63) | (exponent_f64 << 52) | mantissa_f64;
let f64_value = f64::from_bits(f64_bytes);
f64_value
}
Your function looks good (you can golf it to something shorter). The -63 just has to do with how the mantissa and exponent are interpreted. You should imagine you put a "binary decimal point" between the most-significant and second most-significant bits of the mantissa in the 80-byte float. But in a u64 the "binary decimal point" is after the least significant bit, 63 bits away. That's why my formula had it. In an f64 the 1 to the left of the decimal point is implicit. Since you are constructing the float directly you can skip that step.