Jump to content
Dennis Nedry

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

Recommended Posts

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.

Share this post


Link to post
Share on other sites

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):

 

    $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

Edited by Guest

Share this post


Link to post
Share on other sites

VERY COOL, maybe we can figure this out!

 

I've taken each 15th bit, removed it from the audio, and used it as follows:

 

output_audio = audio_data * (0x2C - 15th_bit) * 16

 

And as you can tell by listening to this:

 

https://sites.google.com/site/benboldt/files/700_boot_beep.wav

 

This is not the proper decompression, but it may be on to something because it does sound a lot better than the raw sample.

Share this post


Link to post
Share on other sites

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.

5a1d03c04d333_Picture3.png.6923d5d7d0aad7ff8bf37920c510f610.png

Share this post


Link to post
Share on other sites

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 :)

Share this post


Link to post
Share on other sites

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

 

    $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...

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

5a1d03c05faf6_Picture4.png.499d1e568f769e3b0836d70c22c26a58.png

Share this post


Link to post
Share on other sites

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.

5a1d03c063416_Picture5.png.6b137bca352980ef674d3e06b87bb08f.png

Share this post


Link to post
Share on other sites

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

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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?

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

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!

Share this post


Link to post
Share on other sites

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)

Share this post


Link to post
Share on other sites

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.

Share this post


Link to post
Share on other sites

Interesting observations! So maybe we just have to integrate it...however, the weird "every 15" bytes still have to play into the equation somewhere...

 

The uncompressed LC III startup sound (which is the same sound the Quadra 700 makes at startup according to Mactracker) is 31,178 bytes. The compressed sounds in the Quadra 700 ROM are 20,985 bytes and 16,440 bytes.

 

No idea which one is which, but assuming it's the first (longer) one, it's looking like a compression ratio of about 3:2. I suppose someone with a Quadra 700 could put the smaller sound in place of the bigger one. If a different sound plays, we'd know what the smaller one sounds like...if the same sound plays, we'd know the second smaller sound is the Q700's startup chime and then we could figure out a way to play the bigger sound to find out what it contains. I believe my ROM SIMM is compatible with the Quadra 700...

 

It's possible that in the Quadra 630 ROM they just left a bunch of old code that happened to include the older compressed version of the same sound. I already know they leave a lot of backwards-compatible code in their ROMs -- for example, if I boot a IIci with the Quadra 700 ROM, it plays the IIci's synthesized startup sound (and boots OK). True, it would still be kind of strange to leave both the compressed and uncompressed versions in the ROM though, considering they could just patch the code to use the uncompressed version anyway (the sound player code they include would automatically handle everything correctly if they just pointed to the uncompressed sound header).

Share this post


Link to post
Share on other sites

I created a remarkable white noise generator with the integration approach, patents pending. Don't touch! :o)

 

I have a feeling that this isn't terribly complicated, but also that they did something obscure that we might not easily think of. This is a real shame because it may very well be that the startup sound is the only thing that ever used this cool hardware audio decompression feature.

 

There is a possibility that it was cheaper for Apple to build this decompression into their custom audio chip specifically for the startup sound than it was to store that extra chunk of data in ROM. The extra data may have bumped them up in ROM capacity whereas the audio chip was probably the exact same price with this feature after the engineering was complete.

Share this post


Link to post
Share on other sites

LOL...well it was worth a try! Yeah, I hope it's not too complicated. This is really starting to bug me that we haven't figured it out!

 

I just thought of a few other things. Just pulling ideas out here, not necessarily facts. Since every 15th byte is presumably some control byte or extra info that's not necessarily audio data...

 

The first sound *really* contains 20985*(14/15) = 19586 bytes and the second sound contains 16440*(14/15) = 15344 bytes.

 

Also, I forgot that the uncompressed sound is actually labeled as 22.254-ish KHz while the compressed ones are labeled as 22.050 KHz. If we correct for that when comparing across different sample rates (so thinking of the uncompressed startup sound as having 31178*(22.050/22.2545454) = 30891.4 "equivalent" bytes if it were 22.050 KHz, that's EXTREMELY close to being 2:1 compression for the second smaller sound which would go along with your thoughts about the data possibly being in nibbles...

 

Edit: The second smaller one is DEFINITELY the Quadra 700 startup chime. Check this WAV (and the code I used to generate it) out...

 

All that's missing is figuring out WHAT the byte at the beginning of each 15 byte chunk MEANS! Maybe it's some kind of an amplitude or offset or something? I'm almost thinking an amplitude value. Because if you listen to the end of my WAV, the sound cuts off abruptly, but on the uncompressed version of it, it kind of fades out at the end.

 

#include 
#include 

int main(int argc, char *argv[])
{
FILE *f = fopen("/home/doug/Desktop/Q700RawSoundData2", "rb");
FILE *w = fopen("/home/doug/Desktop/Q700DataOut", "wb");
int counter = 0;
uint8_t headerByte;

while (!feof(f))
{
	uint8_t s;
	fread(&s, 1, 1, f);

	if ((counter % 15) != 0)
	{
		uint8_t s1 = s >> 4;
		uint8_t s2 = s & 0x0F;

		// TODO: What to do with headerByte?
		s1 *= 16;
		s2 *= 16;

		fwrite(&s1, 1, 1, w);
		fwrite(&s2, 1, 1, w);
	}
	else
	{
		headerByte = s;
	}

	counter++;
}

fclose(f);
fclose(w);
}

Share this post


Link to post
Share on other sites

I did the exact same thing with the second sound but failed to share! In fact, if I feed the extra byte through with the equation that I posted a ways back, it sounds even better. The uncompressed sound fades in amplitude at the end and converting this one without the extra byte does NOT fade out, to my ears when I listen and to my eyes when I look at the waveform. I believe what I sense here and this is a very good clue if it's true.

 

I'm beginning to think that this is related to u-Law compression. From what I can tell, the 4-bit nibbles are signed (unlike ulaw) and act as u-Law's interval code, and the extra byte acts like u-Law's range code. This unknown compression seems roughly to structure its 15-byte packets in one of the following ways:

 

1 range nibble and 29 interval nibbles

1 range byte and 28 interval nibbles

1 range nibble, 1 unknown nibble, and 28 interval nibbles

 

u-Law:

http://en.wikipedia.org/wiki/Μ-law_algorithm

 

I have not tried playing this 'snd ' resource as a System 7 sound file on my real Quadra 700. This may be a required test. If we have a way to play these files, then we can easily make slight modifications and note the effects.

Share this post


Link to post
Share on other sites

Instead of:

       // TODO: What to do with headerByte?
        s1 *= 16;
        s2 *= 16;

Try this:

 

       // TODO: Admittedly I still don't know what to do with headerByte!!
        s1 *= 16 * (0x2C - headerByte);
        s2 *= 16 * (0x2C - headerByte);

 

In theory, we should be able to modify the sound slightly in ROM and compare analog recordings of the startup chime. I'll have to look and see if the Q700 has a ROM slot - I think it does.

Share this post


Link to post
Share on other sites

Interesting! I'll check out the uLaw and aLaw stuff. I thought it was mostly meant for voice recordings...

 

I tried the mod you gave, but it doesn't work--I end up with something that doesn't look like a sound waveform anymore.

 

The headerByte values I get are between 0x01 and 0x35. On the longer sound, the range is from 0x00 to 0x3C. I'm almost thinking it's a 6-bit value of some kind (between 0 and 0x3F). Looks like most of the time it's about 0x24 or 0x25, but sometimes you see a spike up or down.

 

Edit: I like the idea of changing it slightly to see how the analog recording changes! I'm also pretty sure the Q700 has a ROM SIMM slot, and I'm also 99% sure it's compatible with the standard II series pinout. I almost want to say someone has tried the programmable SIMM in one already...

Share this post


Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×