• Hello MLAers! We've re-enabled auto-approval for accounts. If you are still waiting on account approval, please check this thread for more information.

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

I have just discovered that types 1, 2, and 3 continue on into the plaintext even after the end of the packet. I realized this by adding one more silent packet to the hacked BootBeep control panel.

This should improve sound quality when I get it in there.

Picture 2.png

 
I have done some work on wave type 2. To overcome the hardware high-pass, I played this wave at maximum volume in increments of 4 samples, each step backing up from a raw audio packet (type 0). The raw audio packet immediately returns the sound output to 0 and does not carry through any types from previous packets.

So I recorded the offset from 0 at each of these increments where the wave abruptly stopped. This is the DC offset caused by the high pass at this particular point in the wave. I did this for all 64 samples in the wave, so 16 times. I then took a screenshot of the entire wave, plotted the offsets above the wave, and did a Photoshop Skew between each point to undo the high-pass.

I have currently only done this for type 2. I have attached the wave, 88.2kHz 1:1. It definitely dips below zero slightly.

wave-type-2.gif

 
The secret sound is almost entirely type 2 waves, so it won't get much better without some other approach. But anyway, I mapped out the un-high-pass of the type 1 wave. This one is about 80 samples and it does not go below zero.

wave-type-1.gif

 
I've done all 3 waves now, and it still doesn't sound quite right. It's only slightly better since I fixed the type 2 wave, barely noticeable difference. I think it's about as close as I can get through this analog sort of hacking.

https://sites.google.com/site/benboldt/files/Q700DataOut4.aif

It seems that when the pulses add together, there is some small proportioning going on. 2 pulses added together aren't as loud as they would be if added 1:1. I think that may be where the problem lies.

wave-type-3.gif

 
Latest C++ source code:

Code:
#include 
#include 
#include 
using namespace std;

#define OUTPUT_DATA_SIZE 100000

int16_t output_data[OUTPUT_DATA_SIZE];
unsigned long int output_data_count = 0;

int main(int argc, char *argv[])
{
FILE *f = fopen("Q700RawSoundData2", "rb");
FILE *w = fopen("Q700DataOut", "wb");

int8_t packet[15];

int16_t sinc_wave[56] = {32767, 32070, 21612, 4880, -11155, -21612, -23355, -17429, -6972, 4532, 12549, 15338, 12549, 5926, -2092, -8017, -10806, -9760, -5926, -697, 4183, 6623, 6623, 4183, 697, -2789, -4880, -5229, -3834, -1743, 1046, 2440, 3137, 2440, 1046, -697, -2092, -2440, -2440, -1394, -349, 697, 1394, 1394, 697, -349, -1046, -1394, -1394, -1046, -697, 0, 349, 697, 349, 0};
int16_t saw_wave[80] = {32767, 30327, 27887, 25795, 24052, 22658, 21264, 20218, 18475, 17778, 16384, 15338, 14292, 13595, 12549, 11852, 11155, 10458, 9760, 9063, 8715, 8017, 7320, 6972, 6275, 5926, 5577, 5229, 4880, 4880, 4532, 4183, 3834, 3834, 3486, 3137, 3137, 2789, 2789, 2440, 2440, 2092, 2092, 2092, 1743, 1743, 1743, 1394, 1394, 1394, 1394, 1046, 1046, 1046, 697, 697, 697, 697, 697, 697, 697, 697, 349, 349, 349, 349, 349, 349, 349, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
int16_t hump_wave[64] = {30496, 32118, 32767, 31794, 31145, 29847, 28225, 26603, 24656, 22710, 21088, 19141, 17519, 15572, 13950, 12653, 11355, 10057, 8759, 7462, 6489, 5515, 4542, 3893, 3244, 2595, 2271, 1622, 1298, 973, 649, 649, 324, 0, 0, 0, -324, -324, -324, -324, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};

int8_t nibble_1, nibble_2;

signed int new_sample;

// Clear output data.
for(int i = 0; i < OUTPUT_DATA_SIZE; i++)
{
	output_data[i] = 0;
}

output_data_count = 0;

while (fread(packet, 1, 15, f)) // Read in entire packets until EOF.
{
	if(0x00 == packet[0] & 0x30) // Previous samples that extend into a direct sample are overridden.
	{
		for(int i = output_data_count; i < output_data_count + 80; i++)
		{
			output_data[i] = 0; // Clear remaining samples.
		}
	}

	for(int i = 1; i < 15; i++)  // for each packet data byte
	{
		// Separate and sign extend each nibble.
		nibble_1 = packet[i] & 0x0F;  // Least significant nibble plays first.
		nibble_2 = (packet[i] & 0xF0) >> 4;
		if(nibble_1 & 0x08) nibble_1 |= 0xF0;  // Sign extend
		if(nibble_2 & 0x08) nibble_2 |= 0xF0;

		switch(packet[0] & 0x30)
		{
			case 0x00:  // Direct samples
				new_sample = (nibble_1 * 512);
				for(int k = (packet[0] & 0x0F); k != 0; k--) new_sample /= 2;  // Can't shift because of signed numbers.
				output_data[output_data_count] = new_sample;
				output_data_count++;

				new_sample = (nibble_2 * 512);
				for(int k = (packet[0] & 0x0F); k != 0; k--) new_sample /= 2;  // Can't shift because of signed numbers.
				output_data[output_data_count] = new_sample;
				output_data_count++;

				break;

			case 0x10:  // Saw wave pulses
				for(int j = 0; j < 80; j++)  // follow through to each remaining sample.
				{
					new_sample = saw_wave[i] * nibble_1;
					for(int k = (packet[0] & 0x0F); k != 0; k--) new_sample /= 2;  // Can't shift because of signed numbers.
					new_sample += output_data[output_data_count + j];

					if(new_sample > 32767) output_data[output_data_count + j] = 32767;
					else if(new_sample < -32768) output_data[output_data_count + j] = -32768;
					else output_data[output_data_count + j] = new_sample;
				}
				output_data_count++;

				for(int j = 0; j < 80; j++)  // follow through to each remaining sample.
				{
					new_sample = saw_wave[i] * nibble_2;
					for(int k = (packet[0] & 0x0F); k != 0; k--) new_sample /= 2;  // Can't shift because of signed numbers.
					new_sample += output_data[output_data_count + j];

					if(new_sample > 32767) output_data[output_data_count + j] = 32767;
					else if(new_sample < -32768) output_data[output_data_count + j] = -32768;
					else output_data[output_data_count + j] = new_sample;
				}
				output_data_count++;
				break;

			case 0x20:  // Hump wave pulses
				for(int j = 0; j < 64; j++)  // follow through to each remaining sample.
				{
					new_sample = hump_wave[i] * 141 / 106 * nibble_1 / 3;
					for(int k = (packet[0] & 0x0F); k != 0; k--) new_sample /= 2;  // Can't shift because of signed numbers.
					new_sample += output_data[output_data_count + j];

					if(new_sample > 32767) output_data[output_data_count + j] = 32767;
					else if(new_sample < -32768) output_data[output_data_count + j] = -32768;
					else output_data[output_data_count + j] = new_sample;
				}
				output_data_count++;

				for(int j = 0; j < 64; j++)  // follow through to each remaining sample.
				{
					new_sample = hump_wave[i] * 141 / 106 * nibble_2 / 3;
					for(int k = (packet[0] & 0x0F); k != 0; k--) new_sample /= 2;  // Can't shift because of signed numbers.
					new_sample += output_data[output_data_count + j];

					if(new_sample > 32767) output_data[output_data_count + j] = 32767;
					else if(new_sample < -32768) output_data[output_data_count + j] = -32768;
					else output_data[output_data_count + j] = new_sample;
				}
				output_data_count++;
				break;

			case 0x30:  // Sinc wave pulses
				for(int j = 0; j < 56; j++)  // follow through to each remaining sample.
				{
					new_sample = sinc_wave[i] * nibble_1 / 5;
					for(int k = (packet[0] & 0x0F); k != 0; k--) new_sample /= 2;  // Can't shift because of signed numbers.
					new_sample += output_data[output_data_count + j];

					if(new_sample > 32767) output_data[output_data_count + j] = 32767;
					else if(new_sample < -32768) output_data[output_data_count + j] = -32768;
					else output_data[output_data_count + j] = new_sample;
				}
				output_data_count++;

				for(int j = 0; j < 56; j++)  // follow through to each remaining sample.
				{
					new_sample = sinc_wave[i] * nibble_2 / 5;
					for(int k = (packet[0] & 0x0F); k != 0; k--) new_sample /= 2;  // Can't shift because of signed numbers.
					new_sample += output_data[output_data_count + j];

					if(new_sample > 32767) output_data[output_data_count + j] = 32767;
					else if(new_sample < -32768) output_data[output_data_count + j] = -32768;
					else output_data[output_data_count + j] = new_sample;
				}
				output_data_count++;
				break;

			default:
				cerr << "Error when parsing sound characteristic nibble.\n";
				break;

		}
	}
}

//cout << output_data_count;
fwrite(output_data, 2, output_data_count, w);

fclose(f);
fclose(w);
}
 
Have a look at this fine specimen. At the transition from one packet to the next, if the type of wave changes, the tail end of the wave transforms into the tail end of the new wave type. VERY interesting. I'm not sure how it connects the waves together yet, but it does appear to be a smooth transition.

This all makes me wish I understood my control systems classes a little bit better.

When these wave "pulses" build on each other, they don't just add together. There's more to it and I can't quite figure it out. There is some strange stuff happening here.

Picture 6.png

 
Type 1 mode is:

Next sample = current sample * (15/16)

And nibbles are instantaneous offsets of the current position of the wave I think.

It could very well be that Type 2 is a filter on top of type 1.

Type 3 (sinc) might be best saved for last with this approach. But it DOES appear to follow type 1 in amplitude.

 
I'm trying to think in terms of piecewise functions - it seems like a chip like this would do simple calculations based on a very short history of the sound, and won't keep track of the sound very far outside of the current position of the sound wave and the current nibble. So for example keeping track of an entire equation and repeatedly feeding through an exponential function from a set starting point is not likely.

Type 3 mode is pretty much a sine wave that uses type 1 mode as an envelope. The exact frequency and starting point of the sine wave is still unknown.

I have a new theory for type 2 mode. It goes like this:

Variable X = The point where the sound wave would jump to in type 0 or 1

Calculate sample:

{

Sample = (X + previous sound wave position) * (7/8)

X = X * (7/8)

}

X is reloaded when any non-zero nibble occurs, relative to the current position of the sound wave.

WHY am I hacking this you may ask? Why NOT I may reply. :b&w:

You can see that this method is not perfect in the image. The yellow line should be on top of the black line.

edit

I was using the wrong scale. In the new image, both are 28 samples, and it's possible that this is correct considering the high-pass on the black lines.

Picture 11.png

Picture 14.png

 
It's ALMOST perfect now! :D

https://sites.google.com/site/benboldt/files/Q700DataOut5.aif

Code:
#include 
#include 
#include 
using namespace std;

int main(int argc, char *argv[])
{
FILE *f = fopen("Q700RawSoundData2", "rb");
FILE *w = fopen("Q700DataOut", "wb");

int16_t cosine_wave[21] = {32767, 27073, 11971, -7291, -24020, -32401, -29522, -16384, 2449, 20430, 31311, 31311, 20430, 2449, -16384, -29522, -32401, -24020, -7291, 11971, 27073};
unsigned int cosine_pointer;

int8_t packet[15];
int8_t prev_type;
int8_t nibble_1, nibble_2;
int16_t raster_1, raster_2;

int16_t sample;
int16_t envelope;
int16_t accumulator;

prev_type = 0x00;  // Init prev_type.
cosine_pointer = 0;  // Init cosine pointer.
envelope = 0;
sample = 0;

while (fread(packet, 1, 15, f)) // Read in entire packets until EOF.
{	
	if(prev_type != packet[0] & 0x30)  // Check to see if the type changed for this packet.
	{
		cosine_pointer = 0;  // Reset cosine wave to origin.
		envelope = sample;  // Reset envelope to current sample.
	}

	if(0x20 != packet[0] & 0x30)  // Check to see if this packet is NOT damp pulse.
	{
		accumulator = 0;
	}

	for(int i = 1; i < 15; i++)  // for each packet data byte
	{
		// Separate and sign extend each nibble.
		nibble_1 = packet[i] & 0x0F;  // Least significant nibble plays first.
		nibble_2 = (packet[i] & 0xF0) >> 4;
		if(nibble_1 & 0x08) nibble_1 |= 0xF0;  // Sign extend
		if(nibble_2 & 0x08) nibble_2 |= 0xF0;

		// Determine decompressed value based on data nibble and header nibble
		raster_1 = (nibble_1 * 4096);
		for(int j = (packet[0] & 0x0F); j != 0; j--) raster_1 /= 2;  // Can't shift because of signed numbers.

		raster_2 = (nibble_2 * 4096);
		for(int j = (packet[0] & 0x0F); j != 0; j--) raster_2 /= 2;  // Can't shift because of signed numbers.

		switch(packet[0] & 0x30)
		{
			case 0x00:  // Direct samples
				sample = raster_1;
				fwrite(&sample, 2, 1, w);

				sample = raster_2;
				fwrite(&sample, 2, 1, w);

				break;

			case 0x10:  // Envelope Only
				sample = (sample + raster_1);
				fwrite(&sample, 2, 1, w);
				sample = sample * 15 / 16;

				sample = (sample + raster_2);
				fwrite(&sample, 2, 1, w);
				sample = sample * 15 / 16;

				break;

			case 0x20:  // Damp Pulse
				if(0 != raster_1) accumulator += raster_1;
				sample = sample + accumulator;
				fwrite(&sample, 2, 1, w);
				accumulator = accumulator * 7 / 8;  // Degrade accumulator
				sample = sample * 7 / 8;

				if(0 != raster_2) accumulator += raster_2;
				sample = sample + accumulator;
				fwrite(&sample, 2, 1, w);
				accumulator = accumulator * 7 / 8;  // Degrade accumulator
				sample = sample * 7 / 8;

				break;

			case 0x30:  // Enveloped Cosine
				if(cosine_pointer > 20) cosine_pointer = 0;  // Loop cosine pointer as necessary.
				if(0 != raster_1)
				{
					envelope += raster_1;
					//cosine_pointer = 0;
				}
				sample = cosine_wave[cosine_pointer] * envelope / 32767;
				fwrite(&sample, 2, 1, w);
				cosine_pointer++;
				envelope = envelope * 15 / 16;

				if(cosine_pointer > 20) cosine_pointer = 0;  // Loop cosine pointer as necessary.
				if(0 != raster_1)
				{
					envelope += raster_1;
					//cosine_pointer = 0;
				}
				sample = cosine_wave[cosine_pointer] * envelope / 32767;
				fwrite(&sample, 2, 1, w);
				cosine_pointer++;
				envelope = envelope * 15 / 16;

				break;

			default:
				cerr << "Error when parsing sound characteristic nibble.\n";
				break;

		}
	}
}

fclose(f);
fclose(w);
}
 
I just realized that this compressed sound is much higher quality than the non-compressed version. decompressing this sound results in 16-bit audio whereas the non-compressed one is only 8-bit. Even where this converter is at right now, the 16-bit one sounds better than the 8-bit, except for the popping.

It may be worth the extra mile to debug this so we have the highest quality startup sample in our collection!

Currently I am not sure about starting points of the cosine wave in type 3, and also I am not completely sure if my cosine wave is exactly the right frequency. Passing from one type to another is an area that can be verified. Also, how peaking is handled, and if calculations are overflowing their integer types in the decompression program.

 
I've been beginning all my other posts with "Wow!" so I'll try to stop. But I can't resist...wow!

You've almost cracked it. That is sounding AWESOME.

Your work on the EASC compression algorithm will undoubtedly be useful in other applications too, such as emulators. I would bet that Arbee would be interested in seeing this stuff for MESS. It's definitely worth it to get it completely working!

Are either of you wearing a deerstalker hat while you're doing this? :)
LOL, I'm not! This is all Dennis Nedry. He's doing some crazy awesome compression algorithm stuff here that I wouldn't even know where to begin to play with!

 
I was bored at work and tried to read this thread . . . I need to hit the reset button on my brain!

I lost it about the time "accumulating deltas" were mentioned. I just thought I'd pipe in to say that this exactly how a PolyLine Font Format we cracked worked back in the day. ROM space was far more precious in the 80's when this format was devised than it was in the Quadra era, or the 68030 era for that matter.

Delta changes with the oddball formatting codes denoting the arcs connecting notes, keys, scales or whatnot seemed like a good fit to me for what you were describing. Annotated deltas from one note to the next would be a simple, compact way to store a sheet of music, just as a simplified version without the annotation was a compact way to store the information needed to plot a letter.

Just thought I'd toss it out there . . . must . . . take . . . catnap . . . :simasimac:

 
I have plotted out the beginning of the normal startup chime. The dark line is the one that went through my program, the lighter line is an audio recording of the Quadra 700 playing it, magenta stripes are the first nibble in a packet, green stripes are the other nibbles, and the number printed on the bottom is the header byte of the packet. The left nibble of the header byte designates the wave type for the entire packet.

As you can see, there are some issues here. It seems that both of the big bumps happen right at the transition from one packet to the next.

Picture-4.gif

 
Back
Top