Help wanted recovering source file

I have a tantalising problem. I have an old magnetic tape (1/4 inch audio tape on a 5" reel). It contains the source to some of my earliest code from around 1976. I thought it would be fun to try to get it back off the tape.

Motorola_Source.png

I fired up my old tape-recorder, and with some difficulty transcribed it onto disk. Then I did a frequency analysis.

(The left and right channels are different files).

You can find a description of the encoding method that (I think) was used here:

http://www.gammon.com.au/Arduino/cassette_interface.pdf (4.2 MB) - Start at the bottom of the first page: "Audio Cassette Interface"

Basically it seems that you should have either 4 cycles of 1200 Hz for a zero bit, and 8 cycles of 2400 Hz for a one bit, recorded at 300 baud. Quite possibly I played the tape back at double speed, as the frequency analysis seems to show 2200 Hz and 4400 Hz as the main frequencies.

I've tried to analyze it with a Perl program here: http://www.cse.dmu.ac.uk/~mward/martin/software/index.html

After tweaking the command line arguments a bit I got tantalizingly close:

Glimpses_of_source.png

But not really good enough. There are lots of bad characters. But surely I don't need to do a FFT analysis to decode a simple signal like that? The original computer was just an 8 bit computer with minimal RAM.

I'm tempted to convert the audio back into digital and just sample it and try to work out the individual bits. Has anyone got any suggestions for an easy way to do this?

Wow - flashback !! Sounds like you are working with the old "Kansas City Standard". I have forgotten all the details, but the decoder was pretty simple and quite a few were published in Byte and Kilobaud magazines.

http://www.dabeaz.com/py-kcs/index.html
http://whats.all.this.brouhaha.com/2012/01/02/code-to-encode-and-decode-kansas-city-standard/

ps - I still have an old model 28 Teletype downstairs that I used to use about the same time frame if you want it :slight_smile:

Sounds like you are working with the old "Kansas City Standard".

Yes, that is correct. I should have mentioned it, but it is mentioned in the PDF.

I used this for my 2650 tape I/f at 300baud , if I remember correctly it was a kcs style signal
can't find the actual device . its probably landfill

Root_1.PDF (12.3 KB)

A lovely little problem in digital signal processing there. Perl is not the language of choice
for that!

I think I replied to your post too fast - when I saw it, you had some of the text there, but no images or attachments. I probably still have some cassette tapes downstairs with who knows what on them either for the VIC-20 or my CP/M system (I remember paying something like $480 each for double sided double density Mitsubishi 8 inch floppy drives. Still have them too actually!!)

Nick:

To decode FSK on a computer, I've recently been playing with two basic DFT approaches. One is an 8-point Fourier transform and the other is an autocorrelation approach, which works a bit better. I implemented both to decode Bell 202 1200 baud FSK (1200/2200 Hz) which is close to what you have.

I digitize off-the-air waveforms at 9600 bits per second, make .wav file and decode them offline using a C program on a PC, using the Code::Blocks compiler. I think either would be work fine for your case.

FT program (easy to change the upper frequency, I'll post the autocorrelation version separately)

//fourier transform version. Does not work quite as well as correlator (higher error rate).
//better with 16 bit coefficients than 8 bit
//from http://ubuntuforums.org/showthread.php?t=968690

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <math.h>
//for Code::Blocks
// project->build options->search directories, add (lib,include)
// linker settings: link libraries, add path to libsndfil\lib\libsndfile.lib
// copy bin\libsndfile-1.dll to source directory
//  ...?? (can't get this to work) statically link by adding to linker options -static
#include <sndfile.h>

#define NPERBAUD 8
#define PI 3.141592653

// uart variables
#define IDLE 1
#define START 2
#define RUN 4
#define RXFLAG 8
#define FRAMERR 16
#define STOP 32
#define PARITY 64

int coeffloi[8],coeffloq[8],coeffhii[8],coeffhiq[8];
void initFSK(void)
{
  int i;

  for (i=0; i<NPERBAUD; i++)
  {
    coeffloi[i] = 16383*cos(2*PI*i/NPERBAUD*1200/1200);
   coeffloq[i] = 16383*sin(2*PI*i/NPERBAUD*1200/1200);
    coeffhii[i] = 16383*cos(2*PI*i/NPERBAUD*2200/1200);
 coeffhiq[i] = 16383*sin(2*PI*i/NPERBAUD*2200/1200);
  }
}

/*
https://sites.google.com/site/wayneholder/attiny-4-5-9-10-assembly-ide-and-programmer/bell-202-1200-baud-demodulator-in-an-attiny10
Sample the incoming FSK signal at 9600 samples/second (8 times the 1200 Hz frequency used in Bell 202 modulation)
Pass this through two digital filters, one tuned to 1200 Hz and the other to 2200 Hz.
The function is called for index = 0 through length(data) - 8
After stepping though at least 8 samples, the value returned from demodulate() will be >0 if it has
demodulated a 2200 Hz tone, or < 0 for a 1200 Hz tone.
*/
      int demodulate (signed char data[]) {
//      static int8_t   coeffloi[] = { 64,  45,   0, -45, -64, -45,   0,  45};
//      static int8_t   coeffloq[] = {  0,  45,  64,  45,   0, -45, -64, -45};
//      static int8_t   coeffhii[] = { 64,   8, -62, -24,  55,  39, -45, -51};
//      static int8_t   coeffhiq[] = {  0,  63,  17, -59, -32,  51,  45, -39};

      int outloi = 0, outloq = 0, outhii = 0, outhiq = 0;
      int ii;
      int sample;
        for (ii = 0; ii < 8; ii++) {
          sample = data[ii];
          outloi += sample * coeffloi[ii];
          outloq += sample * coeffloq[ii];
          outhii += sample * coeffhii[ii];
          outhiq += sample * coeffhiq[ii];
        }
        return (outhii >> 8) * (outhii >> 8) + (outhiq >> 8) * (outhiq >> 8) -
               (outloi >> 8) * (outloi >> 8) - (outloq >> 8) * (outloq >> 8);
      }
int main()
    {
// file handling

    SNDFILE *sf;
    SF_INFO info;
    int num_channels;
    int num, num_items;
    int *buf;
    int f,sr,c;
    int i,j;
    FILE *out;

// decode and uart

    static char rx,ch;
    static short status = IDLE;
    static int clock = 0; // counter for sample
    static int bit = 0; // bit counter
    static char parity=0;
    signed char buf2[8]={0};    //signal samples
    int k,spb=8; //samples per bit

    int result,output,count=0;

    initFSK();

    /* Open the WAV file. */
    info.format = 0;
    sf = sf_open("helicopter_8_9600.wav",SFM_READ,&info);
//    sf = sf_open("CID-test.wav",SFM_READ,&info);
   if (sf == NULL)
        {
        printf("Failed to open the file.\n");
        exit(-1);
        }
    /* Print some of the info, and figure out how much data to read. */
    f = info.frames;
    sr = info.samplerate;
    c = info.channels;
    printf("frames = %d\n",f);
    printf("sample rate = %d\n",sr);
    printf("channels = %d\n",c);
    printf("format = %X\n",info.format);
    num_items = f*c;
    printf("num_items = %d\n",num_items);
    /* Allocate space for the data to be read, then read it. */
    buf = (int *) malloc(num_items*sizeof(int));
    num = sf_read_int(sf,buf,num_items);
    sf_close(sf);
    printf("Read %d items\n",num);

//Write data to filedata.out.
//   out = fopen("filedata.csv","w");
//        fprintf(out,"%d,",(int8_t) ((unsigned int)buf[i]>>24));
//    fclose(out);

    k=0;
    for (i = 0; i < num_items - 8; i += c)
        {
        for(k=0; k<7; k++) buf2[k]=buf2[k+1];  //shuffle to the left
        buf2[7] = (signed char) ((unsigned int)buf[i]>>24);



        result = demodulate(buf2);
        if(result>0) result=0; else result=1;

 // now we can build a uart
 // the byte is delivered in ten bit chunks...
 // in order,
 // start bit (0)
 // bit 0
 // ...
 // bit 7
 // stop bit (1)


 // see what our state is
 if (status & IDLE) // we're idling
 {
 status &= ~RXFLAG;
 status &= ~PARITY;  //clear parity flag
 if (!result) // falling edge of start bit
 {
 status &= ~IDLE; // so we idle no longer
 status |= START; // we're started
 bit = 0; // reset bit counter
 clock = 0; // and the clock count
 parity = 0;
 }
 // else we're still waiting for end of stop bit
 }
 else
 {
 if (status & START) // aha, we got the falling edge
 {
 if ((clock <= spb/2) && (result)) // oops, false trigger...noise perhaps
 {
 status &= ~START;
 status |= IDLE; // so drop back to idle mode
 }
 else
 clock++; // otherwise, one more clock
 if (clock == spb/2) // or are we now in mid start-bit
 {
 status &= ~START;
 status &= ~IDLE;
 status |= RUN; // so now we're hot to trot
 clock = 0; // reset counter
 }
 }
 else
 {
 if (status & RUN) // we're reading data (allegedly)
 {
 if (clock < spb-1) // time for a sample?
 clock++; // not yet
 else
 {
 if (bit < 8) // normal read
 {
 clock = 0;
 rx = rx>>1;
 if (result) {
 rx |= 0x80;
 parity++;  //count 1 bits
                            }
 else rx &= 0x7f;

 bit ++;
 }
 else
 {
 if (! result) // frame error?
 {
 status |= FRAMERR;  //if stop==0
 }
 else
 {
 status &= ~FRAMERR;
 }
 status |= IDLE;
 status |= RXFLAG;
 if (parity&1) status |= PARITY;  //1 if # of 1 bits is odd
 status &= ~RUN;
 status &= ~START;
 }
 }
 }
 }
 }
    if (status & RXFLAG) { //added for debug, sjr
            ch=rx&0x7f;  //remove parity bit
            if(ch == 0x0d) printf("\n");
            if(ch<32 || ch>126) ch='.';
            printf("%c",ch);
            count++;
            if(count>35) {printf("\n"); count=0;}
            } // end if (status & RXFLAG)

 output = (status<<8)+rx;
    }
 return(output);
}

Autocorrelator Bell 202 modem decoder. Note: the optimal time delay depends on the two frequencies. Links to informative articles included in the code. If you want to post an MP3 or WAV file of an example, I would be happy to try decoding it.

BTW Neil Barnes got the following working on an ATTiny10, in assembler!

#include <stdio.h>
#include <stdlib.h>
#include <inttypes.h>
#include <math.h>
int buff[4700000];  //lazy, 4.7 megabyte file!
signed char buf2[2400000];
unsigned char decode[30000];
//for Code::Blocks
// project->build options->search directories, add (lib,include)
// linker settings: link libraries, add path to libsndfil\lib\libsndfile.lib
// copy bin\libsndfile-1.dll to source directory
//  ...?? (can't get this to work) statically link by adding to linker options -static
#include <sndfile.h>

#define NPERBAUD 8
#define PI 3.141592653

// a dll to decode an 8 bit audio data stream into its fsk
// data symbols assuming 1200Hz = 0, 2200Hz = 1, 1200 bps
// and a sample rate of either 7200 or 9600
// http://www.nailed-barnacle.co.uk/callerid/homepage.html
// method explained in Circuit Cellar Sept 2006, issue 194 p18.
// copy of CC article here https://dx.eng.uiowa.edu/eedesign/fskcorr.pdf
// definitions of calls

// replaced FIR filter with faster Chebychev IIR filter, SJR

//#include "nbfsk2.h"
int NbFskDecodeBytes (signed char * audio,
                                long length,
 unsigned char * decode,
 int samples_per_bit);

// uart variables
#define IDLE 1
#define START 2
#define RUN 4
#define RXFLAG 8
#define FRAMERR 16
#define STOP 32
#define PARITY 64

//sbp = 6 (7200) or 8 (9600)

short filter (signed char sample, int spb)
{
int ac;
short input;
short output;
char result;

static short delay[5];
static short b[22]; //dimension of filter_coeff+1

static char rx = 0;
static short status = IDLE;
static int clock = 0; // counter for sample
static int bit = 0; // bit counter
int q;
static int count=0;
static char parity=0;
static char ch;
static short iirX[2]={0},iirY[2]={0};

// input = ((((short)sample)-0x80)<<8); // upscale the sample
    input = sample*256;
 // we delay the input samples_per_bit/2 samples...
 for (q=0; q<(spb/2+1); q++)
 delay[q] = delay[q+1];

 // save new sample
 delay[spb/2] = input;

 // now we multiply todays sample with the delayed one
 ac = (int)delay[0]*(int)input;

 // and finally, low pass filter the result

    iirX[0] = iirX[1];
    iirX[1] = ac>>16; //15
    iirY[0] = iirY[1];
    iirY[1] = iirX[0] + iirX[1] + (iirY[0] >> 2);
        // The above is a simplification of a first-order 600Hz chebyshev filter:
        // iirY[1] = iirX[0] + iirX[1] + (iirY[0] * 0.274338);
 if (iirY[1]>0)
 result = 0;
 else
 result = 1;

 // now we can build a uart
 // the data is delivered in ten bit chunks...
 // in order,
 // start bit (0)
 // bit 0
 // ...
 // bit 7
 // stop bit (1)


 // see what our state is
 if (status & IDLE) // we're idling
 {
 status &= ~RXFLAG;
 status &= ~PARITY;  //clear parity flag
 if (!result) // falling edge of start bit
 {
 status &= ~IDLE; // so we idle no longer
 status |= START; // we're started
 bit = 0; // reset bit counter
 clock = 0; // and the clock count
 parity = 0;
 }
 // else we're still waiting for end of stop bit
 }
 else
 {
 if (status & START) // aha, we got the falling edge
 {
 if ((clock <= spb/2) && (result)) // oops, false trigger...noise perhaps
 {
 status &= ~START;
 status |= IDLE; // so drop back to idle mode
 }
 else
 clock++; // otherwise, one more clock
 if (clock == spb/2) // or are we now in mid start-bit
 {
 status &= ~START;
 status &= ~IDLE;
 status |= RUN; // so now we're hot to trot
 clock = 0; // reset counter
 }
 }
 else
 {
 if (status & RUN) // we're reading data (allegedly)
 {
 if (clock < spb-1) // time for a sample?
 clock++; // not yet
 else
 {
 if (bit < 8) // normal read
 {
 clock = 0;
 rx = rx>>1;
 if (result) {
 rx |= 0x80;
 parity++;  //count 1 bits
                            }
 else rx &= 0x7f;

 bit ++;
 }
 else
 {
 if (! result) // frame error?
 {
 status |= FRAMERR;  //if stop==0
 }
 else
 {
 status &= ~FRAMERR;
 }
 status |= IDLE;
 status |= RXFLAG;
 if (parity&1) status |= PARITY;  //1 if # of 1 bits is odd
 status &= ~RUN;
 status &= ~START;
 }
 }
 }
 }
 }
 //   if (status & RXFLAG) { //added for debug, sjr
 //           ch=rx&0x7f;  //remove parity bit?
 //           if(ch<32 || ch>126) ch='.';
 //           printf("%02x %c %d\n",(unsigned char)rx, ch, parity&1);
 //           } // end if (status & RXFLAG)

 output = (status<<8)+rx;
 return(output);
}

int NbFskDecodeBytes (signed char * audio,
                    long length,
 unsigned char * decode,
 int samples_per_bit)
{
 // this routine takes a block of 8 bit audio data and decodes it
 // using the bell202 fsk standard
 // only bytes which decode without a frame error are saved in 'decode'
 // the calling routine is responsible for ensuring that the decode
 // buffer is sufficiently large - there are about 60-80 samples per
 // byte of output


int q;
int bytes = 0;

struct rec1 {
 char rx;
 char status;
};

 union {
 short rex;
 struct rec1 rec;
} r;

    printf("decode length: %ld, sbp, %d\n",length, samples_per_bit);
 for (q=0; q<length; q++)
 {
 r.rex = filter(audio[q],samples_per_bit);
 if (r.rec.status & RXFLAG)        // aha - a byte is complete
 {
 if (!(r.rec.status & FRAMERR))  // and it has no frame error
 {
 *decode++ = r.rec.rx;
 bytes++;
 }
 }
 }
 return bytes;
}


int main()
    {
    SNDFILE *sf;
    SF_INFO info;
    int num_channels;
    int num, num_items;
    int f,sr,c;
    int i,j,charcount;
    FILE *out;

//    initFSK();


printf("starting...\n");

    /* Open the WAV file. */
    info.format = 0;
    sf = sf_open("helicopter_8_9600.wav",SFM_READ,&info);
//    sf = sf_open("CID-test.wav",SFM_READ,&info);
    if (sf == NULL)
        {
        printf("Failed to open the file.\n");
        exit(-1);
        }
    /* Print some of the info, and figure out how much data to read. */
    f = info.frames;
    sr = info.samplerate;
    c = info.channels;
    printf("frames = %d\n",f);
    printf("sample rate = %d\n",sr);
    printf("channels = %d\n",c);
    printf("format = %X\n",info.format);
    num_items = f*c;
    printf("num_items = %d\n",num_items);
    /* Allocate space for the data to be read, then read it. */
//    buf = (int *) malloc(num_items*sizeof(int));
    num = sf_read_int(sf,buff,num_items);
    sf_close(sf);
//    printf("Read %d items\n",num);
    /* Write the data to filedata.out. */
 //   out = fopen("filedata.csv","w");
    int k=0;

    for (i = 0; i < num_items; i += c)
        {
        buf2[k++] = (signed char) ((unsigned int)buff[i]>>24);
//        if (k<50) printf("%d,\n",buf2[k-1]);
        }
 //   fclose(out);

    num = NbFskDecodeBytes (buf2,
                            k,
                            decode,
 8);
    charcount = 0;
    char ch;
    printf("\ndecoded %d...\n",num);

    for(i = 0; i < num; i++){
        ch=decode[i]&0x7f;
        if (ch == 0x0d) printf("\n");
        if(ch < 32 || ch > 127) ch='.';

        printf("%c",ch);
                charcount++;
   //             if (charcount>50) {
   //               printf("\n");
   //               charcount=0;
   //               }

  //      } //if ch
    }
    printf("\ndone\n");

    return 0;
    }

If you want, digitize the tape audio as a WAV file and use a decent sample rate (at LEAST 22050 if not 44100) and email it to me.

I have several different ways here of decoding audio tones into digital data including a PLL circuit, a frequency to voltage converter and software FFT analysis.

I suspect that the tape speed is not constant enough and it's "fooling" your attempts to recover it. Also, there may be parity bit(s) on each data byte, or CRC / Checksum data at the end of each "block" of data that needs to be recognized and ignored (or if it's simple enough recognized and USED).

I also know Motorola ASM programming from a long time back, so I will easily recognize what's "right" and what doesn't belong.

If you want, send me the stuff and I'll try to recover the data for you.

I'll PM you a username and password that you can use to log into my message board, so you can upload everything there without worries of the file(s) being too large (unless any one file is over 32MB!).

Thanks for the offers of help. I've uploaded one of the tracks (the smaller one) to:

http://gammon.com.au/Arduino/Assembler%20and%20Editor.wav.zip

The file would be too large for an email. It's about 30 MB (sorry) zipped and 41 MB unzipped. I wanted to use .WAV to avoid any artefacts that MP3 might introduce. It was sampled at 44100 Hz.

The tape may have varied speed - it took quite a while to get the capstan to turn at all without slowing down and stopping.

When decoded, the bulk should look like assembler code (for the M6800 chip) with things like LDA A (something), LDX (something), STA A, STX and so on.

Judging by the PDF (supplied in the OP) there should be blocks of data starting with ASCII 'B' followed by a one-byte block length, followed by 2 bytes of address from which the data came, followed by the data, followed by 'G'.

In my most successful attempt the printable part looked like this:

0* EDITOR/ASSEMBLER
P* 12/11/78
p* PART 1
G1JMP GO
* POINTERS
STDATA$4000  
LTDATA$4C00
SYDATA$4C00
 LYDATA$6000
%SODATA[OP
0LODATA[PE
5SMDATA[MT
@LMDATA[ME
E* WORK AREAS

...

* COMMANDS
* --------
* COMMAND: 3 CHARS
* FLAGS: 8 BITS
 * :8 BASE LINE NO
%* :4 RANGE
0* :2 INCREMENT
5* :1 TEXT STRING
@* :8 <<AVAILABLE>>
E* :4 <<AVAILABLE>>
P* :2 <<AVAILABLE>>
U* :1 <<AVAILABLE>>
W* ADDRESS OF  
X* PROCESSING ROUTIN

The code is (or should be) the first assembler I wrote, coded by hand in hex*. Because of the rather extreme limitations of doing such an exercise, variables will only ever be two characters. For example this line looks quite believable:

JMP GO

That looks like the first line in the code, and it would have jumped past all the data (this chip is Von Neumann architecture) to the "main" code (called "GO").

(unless any one file is over 32MB!).

My second file is! Anyway, downloading from my site is easy enough for anyone who wants to check out the tape.

I think I recorded with an OK volume level. I don't see any clipping.

MarkT:
A lovely little problem in digital signal processing there. Perl is not the language of choice for that!

I wouldn't normally even attempt it in Perl, but in an effort to solve it myself that particular page was recommended by another one.


  • Of course, since this is source, it is not the first version. I used the assembler to re-assemble itself. However the bulk of it would have followed the same layout. In particular, I think that to check a variable name against a table, I did something like this:
LDA A first_letter
LDA B second_letter

... iterate through a table of variables, doing:

CMP A,X
BNE not_found
CMP B,X
BEQ found

gpsmikey:
double sided double density Mitsubishi 8 inch floppy drives. Still have them too actually!

Eh? Don't we all? :grinning: :grinning: :grinning:

Of course. Mind you, MP3 encoding is actually doing more-or-less what you want in a fashion!

Hi, Nick:

I think the "Assembler and Editor" wav file was recorded at the wrong speed, or the assumptions about the frequencies used for encoding are wrong. As you already noticed, the two frequencies appear to be roughly 2200 and 4400 Hz, guestimating from the spacing of the peaks. That also agrees with the two major peaks in spectrum plot of your first post. The bit rate appears to around 550 baud, but so far I can't decode anything sensible based on those observations.

What assumptions about frequencies and bit rate did you make in using the PERL program?

See the attached .gif file showing an Audacity screenshot.

I had a wild idea. I used a graphic equalizer to further segregate the high and low frequency parts. I can clearly see the "envelope" of the bits so far.

What I want to try to do is generate square wave data by "detecting" (rectifying the signal around the zero point) the envelope (think AM radio), then low pass it to remove the high frequency components, then square it up by converting the data to either full high or full low.

Then the fun begins.....

I think my best results were with:

baud=550 lo=2200 hi=4400

More testing right now seems to indicate that a baud rate of 555 is about right. I am measuring 440 µs for a zero bit frequency (ie. about 2272 Hz) and 200 µs for a one-bit frequency (ie. about 5000 Hz). This would roughly agree with the standard of 1200/2400 baud doubled because of the playback rate.

I am working on an approach like this: I clean up the incoming signal with an op-amp to be a square wave, and feed that into pin D2. Then I use this code:

#include <SPI.h>
#include <digitalWriteFast.h>

unsigned long tooLong = 500;  // us
unsigned long transition = 300;
unsigned long oneBit = 200;
unsigned long tooShort = 150;

const byte LED = 8;

void risingEdge (void)
  {
  static unsigned long lastTime;
  unsigned long now = micros ();
  unsigned long interval = now - lastTime;
  
  if (interval > tooLong)
    digitalWriteFast (LED, HIGH);
  else if (interval > transition)
    digitalWriteFast (LED, LOW);
  else
    digitalWriteFast (LED, HIGH);

  lastTime = now;
  }
  
void setup() {

  SPI.begin (); 
  SPI.setClockDivider(SPI_CLOCK_DIV8);

  attachInterrupt (0, risingEdge, RISING);
  pinMode (LED, OUTPUT);

  Serial.begin (555);
}  // end of setup



void loop() 
{

  while (Serial.available ())
    {
    digitalWrite(SS, LOW); 
    SPI.transfer (Serial.read ()); 
    digitalWrite(SS, HIGH); 
    }

}  // end of loop

Pin 8 (the "LED" set by the ISR) is fed by a jumper wire into D0 (Rx) so that the serial hardware can try to interpret it. The results are sent out by SPI to another Uno as described here.

Monitoring the other Uno's serial port I am getting sort-of reasonable results:

LDAA$28 STAAVR
 LDAA$FF  STAAVC
 %LDAA$3D 0STAAKC
 5LDAA$2C @STAAVR
 ELDX [IR PSTX IP ULDX SY `STX SS eSTX SC pSTX SE uLDX LY

However it still isn't good. I seem to be missing spaces, newlines, that sort of thing. I thought my interrupt routine idea should be OK. Effectively the LED is set to be on or off depending on the most recent gap, so by the time we get four or eight transitions it should be stable. And the serial hardware should sample in the middle, once it gets a start bit.


There is another possibility. I bought (ages ago) a board that did faster encoding. I don't recall if this tape was made with this board or not. I vaguely remember that it used some fancier encoding system (FSK springs to mind). The likelihood I can find more details about this system is low. However judging by the fact that a lot of bytes seem to be missing, does this sound possible? That the encoding system is more complex than the Kansas City Standard?

jremington:
Hi, Nick:

I think the "Assembler and Editor" wav file was recorded at the wrong speed, or the assumptions about the frequencies used for encoding are wrong. As you already noticed, the two frequencies appear to be roughly 2200 and 4400 Hz, guestimating from the spacing of the peaks. That also agrees with the two major peaks in spectrum plot of your first post. The bit rate appears to around 550 baud, but so far I can't decode anything sensible based on those observations.

What assumptions about frequencies and bit rate did you make in using the PERL program?

See the attached .gif file showing an Audacity screenshot.

I see your using Audacity (me too). Try the "Analyze / Plot Spectrum" option and do an FFT on the data. You will see noise, plus two distinct peaks, one around 2200-2300 hz. and one at 4400-4500 hz.

I tried to see if I could find the bitrate from the FFT, but I realized that an FFT needs periodic data and since the 0 and 1 bits are all over the place in time, they just show up as spectral noise.

As I told Nick, I'm digitally filtered away the 2200 hz area and I have a decent "AM radio like" envelope of 4400 hz "carrier" modulated loud and quiet by the bits. Next I'll "rectify" the data like an AM radio detector and wrap the bottom of the waveform around up to the top. This will give me amplitude variations that I can detect, and it will also double the "carrier" frequency (since the bottom half of the 4400 will be added to the top, but 180 degrees phase shifted, effectively doubling the frequency and making it easier to low-pass out.

I should be left with a sloppy, rounded edge "digital" signal that I can digitally "schmidt trigger" into a nice sharp square wave.

Then I'll have to see if it contains anything recognizable.

This is FUN!

This is the code in my previous post in action:

tape_decoder.png

It seems to me to be detecting the different frequencies correctly. You can (just) see that where the blue line (the output on D8 / input on Rx) is high, the yellow lines are closer together (higher frequency).

The cursor info in this particular case seems to be showing 500 baud.

LDAA, STAA, LDX, STX, LDY, etc... all look like real Motorola 680x stuff.

Even the dollar signs make sense (that's hex - as you know). Have you seen any "immediate" (#) characters or indirect addressing ([..]) characters?

Also, do you know if your original code was CR/LF, or just CR or just LF? I ask because I seem to see two things run together that I would expect to see on separate lines such as:

[b]LDAA $28  ; a memory or IO location, since immediate would be prefaced with a "#"
STAA VR
LDAA $FF  ; a memory or IO location, since immediate would be prefaced with a "#"
STAA VC
[/b]

Tons O'fun!

Do you know how many bits per byte there are?

What I planned to do after I rectify, filter and square up the data is read in the data with C, then find how long bits last and the space between them, then DYNAMICALLY adjust the inter-bit delay to compensate for tape speed differences.

I planned to use the typical method that MCU chips use to detect serial data... wait until a bit SHOULD be there, then take several samples to get a "majority vote" as to whether the bit was 1 or 0.

If you happen to catch, say, a binary 0 with the first sample (because the speed was a bit too high) but the next 7 or 15 or whatever samples give a binary 1, then averaged together you get a 1 that you can be pretty confident about.

Also, do you know if your original code was CR/LF, or just CR or just LF?

I think we'll have to decode it to be honest, because that info would be in the file. :slight_smile:

Probably just a newline, I can't see any advantage in padding out source with CR when memory is incredibly tight.

I seem to be missing bytes with low ASCII values in general (spaces, newlines). That might be some sort of clue. I thought it might be parity, but the manual is pretty explicit that they use no parity (however two stop bits, but that shouldn't affect anything).

Do you know how many bits per byte there are?

The manual says 8N2 (no parity). It needs to be 8 bits because chunks are preceded by a length byte (0 to 255) followed by 2 address bytes. Therefore it must be 8-bit encoding.

I'm starting to lean away from my theory that it is another encoding system. As I recall, the newer encoding board was much faster (which is why I bought it) not just twice as fast. And the fact is that, using the sound file and the example code, I am getting whole words, like "EDITOR/ASSEMBLER" which wouldn't happen if the encoding scheme was totally different.