Skip to main content
Home Forums Found a sound in Quadra 700/900 ROM, but can't extract. Found a sound in Quadra 700/900 ROM, but can't extract.
Thread

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

Found a sound in Quadra 700/900 ROM, but can't extract. Troubleshooting 135 posts Jun 7, 2012 — Dec 31, 2023
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

Wow, that sounds really good! 8-) Can't wait to hear what it sounds like once all the wave types are working.

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

Are either of you wearing a deerstalker hat while you're doing this? :)

More ponderables.

Picture-7.gif

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.

Type 2 behaves kind of like a step response.

Hmmm. Maybe it's a dubstep.

The type 2 curve closely resembles a primitive dissonance curve:

y = x * Exp(-x)

If you plot, use

x range 0 - 1

y range 0 - 0.4

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

I have it to the point where it sounds good now, but it is not perfect by any means when you start comparing the waveform to an actual recording. It's in the very nature of this integration-type decompression to propagate the slightest error into something huge. I believe that it could be perfected, assuming that the sound chip accomplishes its decompression with a completely digital circuit, but I also believe that it's good to realize the point when something becomes good enough to consider moving on.

Here is the latest version of audio and code:

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

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

// Global variable definitions
FILE *f = fopen("Q700RawSoundData2", "rb");
FILE *w = fopen("Q700DataOut", "wb");

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;

void speak(int8_t type, int16_t raster)
{
int16_t cosine_wave[21] = {27073, 32767, 27073, 11971, -7291, -24020,
	-32401, -29522, -16384, 2449, 20430,
	31311, 31311, 20430, 2449, -16384,
	-29522, -32401, -24020, -7291, 11971};

int32_t calc;

if(cosine_pointer > 20) cosine_pointer = 0;  // Loop cosine pointer as necessary.

// Filter for max raster depending on type.
if(0x20 == type) // Damp Pulse
{
	if(raster > 9238) raster = 9238;
	else if(raster < -9238) raster = -9238;
}
else if(0x30 == type) // Enveloped Cosine
{
	if(raster > 20797) raster = 20797;
	else if(raster < -20797) raster = -20797;
}

envelope += raster;
if(0 != raster)
{
	cosine_pointer = 0;
}

switch(type)
{
	case 0x00:  // Direct samples
		sample = raster;
		fwrite(&sample, 2, 1, w);
		break;

	case 0x10:  // Envelope Only
		fwrite(&envelope, 2, 1, w);
		sample = envelope;  // Record this sample for next time.
		break;

	case 0x20:  // Damp Pulse
		accumulator += raster;
		calc = sample + accumulator;
		if(calc > 32767)
		{
			cerr << "0x20 Sample Overflow!" << calc << "\n";
			//calc = 32767;
		}
		else if(calc < -32768)
		{
			cerr << "0x20 Sample Underflow!" << calc << "\n";
			//calc = -32768;
		}
		sample = calc;
		sample = (sample * 7) / 8;
		fwrite(&sample, 2, 1, w);
		break;

	case 0x30:  // Enveloped Cosine
		sample = (cosine_wave[cosine_pointer] * envelope) / 32767;
		fwrite(&sample, 2, 1, w);
		break;

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

envelope = (envelope * 15) / 16;  // Envelope degrades: 15/16 times previous value.

accumulator = (accumulator * 7) / 8;  // Accumulator degrades: 7/8 times previous value.

cosine_pointer++;  // Keep the cosine wave rolling
}

int main(int argc, char *argv[])
{
// Init variables		
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(0x30 != prev_type) cosine_pointer = 1;  // Start at peak of cosine wave.
	envelope = sample;  // Set the envelope to the current position of the sample when changing packet types.

	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.

		speak(packet[0] & 0x30, raster_1);
		speak(packet[0] & 0x30, raster_2);
	}

	prev_type = packet[0] & 0x30;
}

fclose(f);
fclose(w);
}
Yeah, I know exactly what you're saying about the accumulating error. I think the fact that you've gotten it this far is awesome enough! You basically have a perfect recording of the sound you were looking for originally, too (I don't hear any scratches or anything in that one). Congrats!

Most of the remaining issues come from where one packet hands off to the next, even if the packet type remains the same from one to the next.

I think that you have to partially calculate the next state in the current state.

I've cleaned up the code and it actually ended up making an extremely slight improvement to the sound. But the code is simpler now.

In the image the I posted that shows each packet with magenta lines - it looks pretty much like those magenta lines are supposed to move two spaces to the left. I'm not sure why. Maybe the sound program I'm using snips off the first 2 samples.

My latest theory is that type 3 should not use a table - somehow it should take a similar approach to the other waves where they are actually calculated out.

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

// Global variable definitions
FILE *f = fopen("Q700RawSoundData2", "rb");
FILE *w = fopen("Q700DataOut", "wb");

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 delta;

void speak(int8_t type, int16_t raster)
{
int16_t cosine_wave[21] = {27073, 32767, 27073, 11971, -7291, -24020,
	-32401, -29522, -16384, 2449, 20430,
	31311, 31311, 20430, 2449, -16384,
	-29522, -32401, -24020, -7291, 11971};

if(cosine_pointer > 20) cosine_pointer = 0;  // Loop cosine pointer as necessary.

// Filter for max raster depending on type.
if(0x20 == type) // Damp Pulse
{
	if(raster > 9238) raster = 9238;
	else if(raster < -9238) raster = -9238;
}
else if(0x30 == type) // Enveloped Cosine
{
	if(raster > 20797) raster = 20797;
	else if(raster < -20797) raster = -20797;
}

if(0 != raster)  // Reset cosine wave with any non-zero nibble.
{
	cosine_pointer = 0;
}

switch(type)
{
	case 0x00:  // Direct samples
		envelope = raster;
		fwrite(&envelope, 2, 1, w);

		delta = 0;
		break;

	case 0x10:  // Envelope Only
		delta = -(raster);
		envelope -= delta;
		fwrite(&envelope, 2, 1, w);

		envelope -= envelope / 16;
		break;

	case 0x20:  // Damp Pulse
		delta -= raster;
		envelope = envelope - delta;
		fwrite(&envelope, 2, 1, w);

		delta -= delta / 8;
		envelope -= envelope / 8;
		break;

	case 0x30:  // Enveloped Cosine
		// I suspect that this type should calculate on-the-fly without a table.
		delta = -(raster);
		envelope -= delta;
		sample = (cosine_wave[cosine_pointer] * envelope) / 32767;
		fwrite(&sample, 2, 1, w);

		envelope -= envelope / 16;
		break;

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

cosine_pointer++;  // Keep the cosine wave rolling
}

int main(int argc, char *argv[])
{
// Init variables		
prev_type = 0x00;  // Init prev_type.
cosine_pointer = 0;  // Init cosine pointer.
envelope = 0;
sample = 0;
delta = 0;

while (fread(packet, 1, 15, f)) // Read in entire packets until EOF.
{	
	if(0x30 != prev_type)
	{
		cosine_pointer = 0;  // Start at peak of cosine wave.
	}
	else if(0x30 != packet[0] & 0x30)  // prev type was 30 and this type is NOT 30.
	{
		envelope = sample;
	}

	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);
		raster_1 /= 1 << (packet[0] & 0x0F);  // Can't shift directly because of signed numbers.

		raster_2 = (nibble_2 * 4096);
		raster_2 /= 1 << (packet[0] & 0x0F);  // Can't shift directly because of signed numbers.

		speak(packet[0] & 0x30, raster_1);
		speak(packet[0] & 0x30, raster_2);
	}

	prev_type = packet[0] & 0x30;
}

fclose(f);
fclose(w);
}
Actually it improved it more than I thought! I keep track of the "delta" variable better when switching between packet types now. I still don't know what the HECK is going on in the 5th packet though.

Picture-4.gif

Well, if I was a good engineer, I would know when to stop. :p But hey, this isn't work, it's for fun, so too bad. ;)

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

I can no longer hear any problems in the sound with my ears.

Some of the differences are probably because this is an analog recording with a high-pass, but there are clearly some spots that are logical issues.

The big change that I made was that I calculate the sinc wave directly using deltas now instead of scaling a cosine table. There are no longer any tables in the converter at all.

I'm betting that the remaining issues have to do with using previous/next state of variables, i.e. updating them before or after using them to modify the output. Maybe rounding could be an issue too, who knows.

I can't believe how concise the code has gotten! That's a really good sign for its accuracy.

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

// Global variable definitions
FILE *f = fopen("Q700RawSoundData2", "rb");
FILE *w = fopen("Q700DataOut", "wb");

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

int16_t sample;
int16_t envelope;
int16_t delta;

void speak(int8_t type, int16_t raster)
{	
// Filter for max raster depending on type.
if(0x20 == type) // Damp Pulse
{
	if(raster > 9238) raster = 9238;
	else if(raster < -9238) raster = -9238;
}
else if(0x30 == type) // Enveloped Cosine
{
	if(raster > 20797) raster = 20797;
	else if(raster < -20797) raster = -20797;
}

switch(type)
{
	case 0x00:  // Direct samples
		envelope = raster;
		fwrite(&envelope, 2, 1, w);
		delta = 0;
		break;

	case 0x10:  // Envelope Only
		delta = -(raster);
		envelope -= delta;
		fwrite(&envelope, 2, 1, w);
		envelope -= envelope / 16;
		break;

	case 0x20:  // Damp Pulse
		delta -= raster;
		envelope -= delta;
		fwrite(&envelope, 2, 1, w);
		delta -= delta / 8;
		envelope -= envelope / 8;
		break;

	case 0x30:  // Enveloped Cosine
		delta -= raster;
		delta += (envelope / 3);
		envelope = (envelope - delta) - (envelope - delta) / 8;
		fwrite(&envelope, 2, 1, w);
		break;

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

int main(int argc, char *argv[])
{
// Init variables		
envelope = 0;
sample = 0;
delta = 0;

while (fread(packet, 1, 15, f)) // Read in entire packets until EOF.
{	
	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 values based on data nibbles and header nibble
		raster_1 = (nibble_1 * 4096) / (1 << (packet[0] & 0x0F));
		raster_2 = (nibble_2 * 4096) / (1 << (packet[0] & 0x0F));

		speak(packet[0] & 0x30, raster_1);
		speak(packet[0] & 0x30, raster_2);
	}
}

fclose(f);
fclose(w);
}
Picture-10.gif

Excellent analysis and coding. Really appreciate the montage you just posted showing the evolution of the sound work.

Three Cheers!!

All of the scratches are gone...woohoo! I agree--that montage is great!

So is this compression algorithm fairly typical of something you'd expect to see in the audio compression world back in those days? Does it lend itself to a particular type of sound?

There are some guys here that probably know a lot about old hardware. I have an appreciation for it but I wasn't around for deep electronic stuff back then - I'm in my mid twenties.

I posted the code in color-coded form here, along with a little blurb/summary about the whole thing:

http://www.d.umn.edu/~bold0070/projects/easc_compression/

I try to post cool stuff on there for when people ask me what I do and what I've been up to.

Here are the 3 wave types graphed in Excel - this is where I experimented to find the right values. All three are given the same first nibble, then the rest of the nibbles are all 0. So this is basically a unit step response for all 3 wave types.

Maybe the blue wave (type 1) starts 1 sample too soon?

Picture 11.png

mp.ls