• Updated 2023-07-12: Hello, Guest! Welcome back, and be sure to check out this follow-up post about our outage a week or so ago.

Found a sound in Quadra 700/900 ROM, but can't extract.

Dennis Nedry

Well-known member
If you drop a Quadra 700/900 ROM file onto HexEdit and find this selection (approximately):

0xBE344 : 0xC342B

This is a special sound that might be currently talked about in some sort of trivia thread elsewhere on this board. :-x

I can't figure out the encoding of this sound. If you listen to it as raw audio using 8-bit, mono, signed, 11025 kHz, you can sort-of hear it. This data is most likely signed because many numbers close to 00 and FF get packed in around each other.

Each 15th byte follows an unknown-to-me pattern in this sound. And in fact, if you locate the next sound stored in ROM which is the normal Quadra startup sound, it too is encoded in this way. Listening to the Quadra one amongst the noise and comparing to the "real" Quadra sound, the pitch is too low. It just so happens that if you multiply 11025 kHz by the fraction (15/14), which compensates for an "extra" byte each 15 bytes, it plays in tune. This is 11812.5 kHz.

So I looked around and learned that the Quadra 700 uses the Enhanced Apple Sound Chip. Could this be raw data that is sent to the chip? Could the extra byte be a volume setting, potentially improving precision at 8-bit depth? I don't know, but it would sure be cool to figure this out so that we could produce a raw sample of this alternate startup sound, and maybe even a higher quality of the Quadra sound. We have most sounds in raw extracted form already.

 

dougg3

Well-known member
Hmm, interesting!

It looks like the 'snd ' resource header associated with that sound begins at 0x000BE2F0. It appears to be a compressed sound sample. The Sound Manager documentation talks about it a bit...I think I saw a similar weirdly-composed sound in the LC III ROM and never got around to figuring out what it was for, so this is a good opportunity!

My interpretation of the header (it's very similar to an example on page 2-78 of Inside Macintosh: Sound):

Code:
    $0001 --> snd resource format 1
   $0001 --> number of data formats in this resource (1)
   $0005 --> data format 1: sampled sound data
$00000080 --> initialization option: initMono
   $0001 --> number of sound commands that follow (1)
   $8051 --> command 1 = bufferCmd, high bit set is dataOffsetFlag, meaning sound data is stored in sound resource
   $0000 -> param1 = 0 (unused by bufferCmd)
$00000014 --> param2 = $14 (since dataOffsetFlag is set, sound header begins at $14 which, coincidentally, is the next data...)
SAMPLED SOUND HEADER BEGINS HERE...
$00000000 --> pointer to data (it follows immediately)
$00000001 --> number of channels in sample
$56220000 --> sample rate of sound (22050.0 Hz in 16.16 fixed point)
$00000000 --> starting of sample's loop point (not used)
$00000000 --> ending of sample's loop point (not used)
     $FE --> compressed sample encoding
     $3C --> baseFrequency at which sample was taken (not used, I think, but $3C = 60)
$00000577 --> number of frames in sample (1399)
$400DAC44000000000000 --> AIFFSampleRate of 22050 Hz in 1.15.64 (sign/exponent/mantissa) floating point? I don't have an easy calculator to verify that
My head hurts too much to finish the decoding right now, but it looks like it's a compressed sound that we can definitely figure out how to decompress and play...

Edit: I figured out the next bytes, $400DAC44000000000000 is an 80-bit floating point. (1 sign bit, 15 exponent bits, 64 mantissa bits) that works out to 22050 Hz...I *think*. Whew

 
Last edited by a moderator:

Dennis Nedry

Well-known member
Here's what the Resorcerer "snd " Template does to it.

I know I've used something that extracts resources from Mac ROMs into a ResEdit file. Maybe that could extract this for us if we can find that program.

Picture 3.png

 

dougg3

Well-known member
Ah...looks like Resourcerer doesn't know how to go further into the 'snd ' record, but my initial interpretation seems ok. Have you tried taking the data and putting it in a 'snd ' resource and playing it? I bet the Mac OS can play it...

I'm still wanting to manually go through and figure out the compression scheme and everything once my head isn't killing me. I'm sure we'll figure it out because it's all stuff that Apple has documented, luckily :)

 

dougg3

Well-known member
Oh if only it were that easy. I've finished going through the sound format:

Code:
    $0001 --> snd resource format 1
   $0001 --> number of data formats in this resource (1)
   $0005 --> data format 1: sampled sound data
$00000080 --> initialization option: initMono
   $0001 --> number of sound commands that follow (1)
   $8051 --> command 1 = bufferCmd, high bit set is dataOffsetFlag, meaning sound data is stored in sound resource
   $0000 -> param1 = 0 (unused by bufferCmd)
$00000014 --> param2 = $14 (since dataOffsetFlag is set, sound header begins at $14 which, coincidentally, is the next data...)
SAMPLED SOUND HEADER BEGINS HERE...
$00000000 --> pointer to data (it follows immediately)
$00000001 --> number of channels in sample
$56220000 --> sample rate of sound (22050.0 Hz in 16.16 fixed point)
$00000000 --> starting of sample's loop point (not used)
$00000000 --> ending of sample's loop point (not used)
     $FE --> compressed sample encoding
     $3C --> baseFrequency at which sample was taken (not used, I think, but $3C = 60)
$00000577 --> number of frames in sample (1399)
$400DAC44000000000000 --> AIFFSampleRate of 22050 Hz in 1.15.64 (sign/exponent/mantissa) floating point? I don't have an easy calculator to verify that
$00000000 --> markerChunk = 0, not used and is supposed to be NIL
$00000000 --> format (unused because compressionID is not fixedCompression)
$00000000 --> futureUse2 = 0, supposed to be zero
$00000000 --> stateVars pointer = NIL
$00000000 --> leftOverSamples pointer = NIL
   $0006 --> compressionID = 6 (UNKNOWN????)
   $0078 --> packetSize = 120 bits per packet
   $0000 --> snthID = 0 (unused)
   $0010 --> sampleSize (16 bits per non-compressed sample)
Next is the sound data, starting at $BE344
So basically, it looks like there are 1399 packets, with 120 bits (15 bytes) per packet, so the total length of bytes of the sound data after the header is probably 1399 * 15 = 20985 bytes.

Sadly, I can't find any documentation for compressionID 6. The only defined ones I can see are:

0 = notCompressed

-1 = fixedCompression

-2 = variableCompression

1 = ACE 2:1

2 = ACE 8:3

3 = MACE 3:1

4 = MACE 6:1

If we can figure out what compressionID 6 means, then I think we're home free...

 

Dennis Nedry

Well-known member
I did try playing that sound resource in Resorcerer, but I was not exactly sure where to cut off the data. I'll see if I can use the info you posted to do that more accurately tomorrow. When I tried playing, the play button held down for a second, no sound, and in subsequent tries, the button did not hold down until I restarted Resorcerer... But anyway, I'll give that a try, and I might even try packing it into a system 7 sound file and give it a try playing it that way.

I thought for some reason, and this may be coming from thin air, but I thought that there were options for uLaw compressions. I may have seen this in sndSampler, I'll give that a check too.

 

Dennis Nedry

Well-known member
It just so happens that this is PRECISELY where I trimmed the "snd " resource before. I saved it into a system 7 sound file and tried opening it in sndSampler, and I got an error (see attachment). I named this file "Secret Chime" - so that name didn't get extracted form anywhere. sndSampler needs some sort of extension to use some compressions, though, and it complains about this when you try to import raw audio manually. So I'll see if I can find something in the manual. The manual is pretty sarcastic, so I think a quick skim might be in order.

Picture 4.png

 

Dennis Nedry

Well-known member
Here's an excerpt from the manual:

How come SndSampler can't Export/Import/Segment IMA 4:1 sounds?
Well, IMA 4:1 compression is kind of funny. (That's funny strange, not funny ha-ha.) First of all, you can't export a sound using IMA 4:1 compression because every computer system implements it differently. That means that Apple's IMA 4:1 is different from Windoze IMA 4:1. So different, in fact, that we're not going to bother trying to do the conversion. This is also the reason you can't import IMA 4:1 sounds. Now, the reason you can't segment an IMA 4:1 AIFF has to do with the nature of the ADPCM (Adaptive Differential Pulse-Code Modulation, as it is known in geek-speak, upon which IMA 4:1 is based) algorithm. With ADPCM it is the difference between succeeding samples which is stored, not the samples themselves. So, taking a somewhat oversimplified look at the situation, if you segment an IMA AIFF and then go about changing the samples in one segment, the next segment will get all screwed up because the samples in the first segment have changed. Remember, it is the differences which are stored in the AIFF file. If you change the last sample in the first segment, then the first sample in the second segment will be wrong, because it expects the last sample in the first segment to still be the same. And then every succeeding sample that segment will be wrong. Yeah, it's kind of complicated, so you'll just have to trust us on this one. (Another reason of course has to do with our innate indolence.)
I think that's total BS, he could have added an extra window with some options that would allow you to specify all of these things and open these files. I guess I don't like this guy but he made a pretty cool sound app. I have to give him credit.

Attached is an image when I try to import raw audio using compression in sndSampler.

Picture 5.png

 

Dennis Nedry

Well-known member
I packaged this data into a WAVE file, which can specify several different compressions, but I haven't had any luck playing it. Wave is little-endian, which may be a major issue with this approach. Being that this came form a Mac ROM, there's a really good chance that the data is big-endian. The file is here if you want to try tweaking. It sounds pretty terrible the way it is:

https://sites.google.com/site/benboldt/files/sound_attempt.wav

A good source for wave header info is here:

http://www.sonicspot.com/guide/wavefiles.html

The file is currently set to IMA ADPCM encoding, 16-bit, 22050 Hz

 

dougg3

Well-known member
Ah yes, I remember seeing aLaw, uLaw, and IMA 4:1. It sounds to me like of all of those, IMA 4:1 would be more appropriate for the startup chime (I think the other two are used mainly for spoken voices?), but it looks like you've experimented a bit with that to no avail. Agreed, it's almost certainly big-endian (assuming the samples are handled more than a byte at a time such that endianness matters)

I tried playing the resource with ResEdit and also dropping it into a sound file. Didn't work in either case. Oh well...

Even if we can't figure out which compression algorithm it actually is, I suspect that we can reverse engineer the algorithm used to decompress it by finding the startup chime code in the ROM. I would guess that the boot code takes the compressed chime and ends up with raw samples being written to the ASC. Then we can use that same algorithm to decompress the other sound that isn't the standard Q700 chime.

 

dougg3

Well-known member
One other thing -- I just went back through my notes when I was disassembling the LC III's ROM and figuring out how it plays its sampled startup chime for my IIci project. There appear to be a few sounds like the one you found. In fact, one of them is the exact same length and it's also that weird compressionID 6, so it's possibly the exact same sound. The snd header for it starts at $BD4C4 in the LC III ROM, and another one starts at $C2722. When I was disassembling the ROM, I took note of the weird sound header (at the time I didn't know that the $FE in the header meant it was a compressed sound and thus followed a different header format, so I wrote a bunch of stuff like "WTF is this sound header?" in my comments).

The good news is that during my disassembly, I left myself a bunch of notes about what the code does. I actually have the code disassembled to the point where it decides whether to play one of the compressed sounds or the LC III sampled sound (which is stored uncompressed in the ROM). I'm guessing if I follow a code path for one of the compressed sounds, I will be able to trace and see what they do to decompress it. Now that I know how the ASC works, I can also probably pick the raw samples out. That is, unless they are using some weird hardware capabillity of the ASC other than dumping raw uncompressed samples into the chip.

 

dougg3

Well-known member
I just finished tracing it. Unfortunately, I have bad news.

The decompression appears to be implemented in hardware...

If it finds a compressed sound header, it loads up the number of bytes (which I did calculate correctly because that's how the ROM does it too), sampling rate, and compression type. Then, depending on the compression type (0x0006 in this case), it writes a value into registers (ASC_BASE + $F08 and ASC_BASE + $F28) ($02 in this case, ORed with 0x80 to become $82 -- perhaps the $80 flag is a "compression enable" flag?) before using the exact same "dumb" "keep writing samples into the ASC being careful not to overflow the FIFO" algorithm that I had already traced out for uncompressed sampled sounds.

(To be exact in my wording, the setup where it writes a value into $F08 and $F28 varies depending on which ASC version it's using, but after the chip initialization it all comes down to writing the compressed data directly into the chip)

So yeah, it's setting up the sound chip for some special mode to accept compressed data, and then writing the compressed data directly to it. :-( All we can do is guess what the compression format is...

Edit: I tried hacking the 'snd ' resource to specify 3:1 or 6:1 compression(I believe these correspond to MACE 3:1 and MACE 6:1) and the sound played in ResEdit without error but was static nonsense. So I don't believe they are MACE compressed.

And, final edit tonight: I see what you're saying about the every 15 bytes thing. It was easy to see at the end of the sound where there is '0C' followed by fourteen '00's over and over again, presumably supposed to be silence. Since the format says it's in 15-byte chunks, that makes perfect sense. It looks like the first byte of each chunk is an indicator of SOMETHING, and then the rest of the 14 bytes per chunk are sound data. Hmm!

OK, I lied, one more edit: Since the sample rate is 22,050, I actually think it writes each byte it encounters into the ASC *three* times (consecutively -- so it writes the first byte 3 times, then the next byte 3 times, then the next one 3 times, ...), if that means anything. That's just part of the algorithm: If the sample rate of the sound is 22254 Hz, it writes each sample once. If the sample rate is 11027 Hz, it writes each sample twice. If the sample rate is anything else, it writes each sample three times (this is what the behavior should be in this case). This might be a clue?

 

dougg3

Well-known member
Just one more correction -- actually, whether or not it writes each sample into the chip 2 or 3 times also depends on which version of the sound chip is in use. Some versions of the chip do it, and some don't. So it's possible that the revision in use on the Quadra 700 only writes each sample once. I didn't catch that last night.

 

Dennis Nedry

Well-known member
I suppose we have both the compressed sample of the normal Quadra sound from this ROM and the raw form of the same sound from other ROMs. There may be some clues if we compare the two. It seems highly doubtful that this is a lossless compression so that may complicate things. We're not dealing with an encryption here, we can basically "hear" the decompressed sound if we play the data dump. I think we can figure this out, and what a great thing to document for emulation purposes!

 

olePigeon

Well-known member
I wonder what the point of having a compressed version of the startup bong was for. Maybe to fit the ROM onto a smaller chip?

 

dougg3

Well-known member
Good point! Comparing it against the uncompressed version of the same sound will be very useful and could give us clues.

I'm suspecting that the other sound is the original LC startup sound, but I don't know for sure...I suppose finding any snd resources in the LC ROM would let us know for sure.

Yes, I'm guessing the reason for using compression is just to make it take up less space in the ROM. I doubt it is for obfuscation purposes or anything like that. In the II series, the synthesized death chime and startup chime all use the same code and just differ in a few small initialization parameters--similarly, probably to save valuable ROM space.

Another possibility would be to disassemble the Sound Manager/plugins and see if any of the public APIs use that same compression mode of the sound chip (doubtful though)

 

Dennis Nedry

Well-known member
The best that I have been able to do with decompressing these sounds always results in a very high-pass sound. This makes me wonder if the data is stored in differential form, because when you differentiate a signal, it tends to high-pass it. This makes me think that maybe we want to accumulate the samples rather than just calculating each one independently and moving on to the next.

Also, it appears that the encoded data MAY be stored in 4-bit nibbles. When I changed my process to create a sample from each 4-bits instead of 8, it did not degrade the quality. This also makes sense, because when played in 8-bit mode the sound must be run at 11kHz to sound right. In the header, it says that this sound is 22kHz.

Also to be noted is the Quadra 630 ROM - it contains both compressed sounds and the uncompressed Quadra sound, all 3 right in a row. Now why in the world they would put a compressed and uncompressed version of the same sound in the same ROM - I'm not sure.

 
Top