/* c2t, Code to Tape|Text, Version 0.995, Tue May 22 22:11:12 GMT 2012 Parts copyright (c) 2011, 2012 All Rights Reserved, Egan Ford (egan@sense.net) THIS CODE AND INFORMATION ARE PROVIDED "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A PARTICULAR PURPOSE. Built on work by: * Mike Willegal (http://www.willegal.net/appleii/toaiff.c) * Paul Bourke (http://paulbourke.net/dataformats/audio/, AIFF and WAVE output code) * Malcolm Slaney and Ken Turkowski (Integer to IEEE 80-bit float code) * Lance Leventhal and Winthrop Saville (6502 Assembly Language Subroutines, CRC 6502 code) * Piotr Fusik (http://atariarea.krap.pl/x-asm/inflate.html, inflate 6502 code) * Rich Geldreich (http://code.google.com/p/miniz/, deflate C code) * Mike Chambers (http://rubbermallet.org/fake6502.c, 6502 simulator) License: * Do what you like, remember to credit all sources when using. Description: This small utility will read Apple I/II binary and monitor text files and output Apple I or II AIFF and WAV audio files for use with the Apple I and II cassette interface. Features: * Apple I, II, II+, IIe support. * Big and little-endian machine support. o Little-endian tested. * AIFF and WAVE output (both tested). * Platforms tested: o 32-bit/64-bit x86 OS/X. o 32-bit/64-bit x86 Linux. o 32-bit x86 Windows/Cygwin. o 32-bit x86 Windows/MinGW. * Multi-segment tapes. Compile: OS/X: gcc -Wall -O -o c2t c2t.c Linux: gcc -Wall -O -o c2t c2t.c -lm Windows/Cygwin: gcc -Wall -O -o c2t c2t.c Windows/MinGW: PATH=C:\MinGW\bin;%PATH% gcc -Wall -O -static -o c2t c2t.c Notes: * Virtual ][ only supports .aif (or .cass) * Dropbox only supports .wav and .aiff (do not use .wave or .aif) Not yet done: * Test big-endian. * gnuindent * Redo malloc code in appendtone Thinking about: * Check for existing file and abort, or warn, or prompt. * -q quiet option for Makefiles * autoload support for basic programs Bugs: * Probably */ #if defined(_WIN32) #include "miniz_win32.h" #else #include "miniz.h" #endif #include #include #include #include #include #include #include #include #define ABS(x) (((x) < 0) ? -(x) : (x)) #define VERSION "Version 0.996" #define OUTFILE argv[argc-1] #define BINARY 0 #define MONITOR 1 #define AIFF 2 #define WAVE 3 #define DSK 4 #define WRITEBYTE(x) { \ unsigned char wb_j, wb_temp=(x); \ for(wb_j=0;wb_j<8;wb_j++) { \ if(wb_temp & 0x80) \ appendtone(&output,&outputlength,freq1,rate,0,1,&offset); \ else \ appendtone(&output,&outputlength,freq0,rate,0,1,&offset); \ wb_temp<<=1; \ } \ } void usage(); char *getext(char *filename); void appendtone(double **sound, long *length, int freq, int rate, double time, double cycles, int *offset); void Write_AIFF(FILE * fptr, double *samples, long nsamples, int nfreq, int bits, double amp); void Write_WAVE(FILE * fptr, double *samples, long nsamples, int nfreq, int bits, double amp); void ConvertToIeeeExtended(double num, unsigned char *bytes); uint8_t read6502(uint16_t address); void write6502(uint16_t address, uint8_t value); unsigned char ram[65536]; int square = 0; typedef struct seg { int start; int length; int codelength; unsigned char *data; char filename[256]; } segment; int main(int argc, char **argv) { FILE *ofp; double *output = NULL, amp=0.75; long outputlength=0; int i, c, model=0, outputtype, offset=0, fileoutput=1, warm=0, dsk=0, noformat=0, k8=0, qr=0; int autoload=0, basicload=0, compress=0, fast=0, cd=0, tape=0, endpage=0, longmon=0, rate=11025, bits=8, freq0=2000, freq1=1000, freq_pre=770, freq_end=770; char *filetypes[] = {"binary","monitor","aiff","wave","disk"}; char *modeltypes[] = {"\b","I","II"}; char *ext; unsigned int numseg = 0; segment *segments = NULL; opterr = 1; while((c = getopt(argc, argv, "12vabcftdpn8meh?lqr:")) != -1) switch(c) { case '1': // apple 1 rate = 8000; model = 1; break; case '2': // apple 2 model = 2; break; case 'v': // version fprintf(stderr,"\n%s\n\n",VERSION); return 1; break; case 'a': // assembly autoloader model = 2; autoload = 1; break; case 'b': // basic autoloader model = 2; basicload = autoload = 1; break; case 'c': // compression model = 2; autoload = compress = 1; break; case 'f': // hifreq rate = 48000; model = 2; autoload = fast = 1; cd = k8 = 0; break; case 'd': // hifreq CD rate = 44100; bits = 16; amp = 1.0; model = 2; cd = autoload = 1; fast = k8 = 0; break; case 't': // 10 sec leader tape = 6; amp = 1.0; break; case 'm': // drop to monitor after load warm = 1; break; case 'e': // end on page boundary endpage = 1; break; case 'p': // stdout fileoutput = 0; break; case 'n': noformat = 1; break; case '8': // 8k rate = 48000; model = 2; autoload = k8 = 1; fast = cd = 0; break; case 'h': // help case '?': usage(); return 1; case 'q': // qr code support rate = 48000; model = 2; autoload = k8 = qr = 1; fast = cd = 0; break; case 'l': // long mon lines longmon = 1; break; case 'r': // override rate for -1/-2 only rate = atoi(optarg); autoload = basicload = k8 = qr = fast = cd = 0; break; } if(argc - optind < 1 + fileoutput) { usage(); return 1; } // read intput files fprintf(stderr,"\n"); for(i=optind;i 1 || model == 1) { if(autoload) fprintf(stderr,"WARNING: number of segments > 1 or model = 1: autoload and fast disabled.\n\n"); autoload = fast = 0; } if(fileoutput) { if((ext = getext(OUTFILE)) == NULL) { usage(); return 1; } else { if(strcmp(ext,"aiff") == 0 || strcmp(ext,"aif") == 0) outputtype = AIFF; else if(strcmp(ext,"wave") == 0 || strcmp(ext,"wav") == 0) outputtype = WAVE; else if(strcmp(ext,"mon") == 0) outputtype = MONITOR; else { usage(); return 1; } } } else { /* if(!model) outputtype = MONITOR; else outputtype = AIFF; */ outputtype = MONITOR; } if(outputtype != MONITOR && !model) { fprintf(stderr,"\nYou must specify -1 or -2 for Apple I or II tape format, exiting.\n\n"); return 1; } // TODO: check for existing file and abort, or warn, or prompt ofp=stdout; if(fileoutput) { if ((ofp = fopen(OUTFILE, "w")) == NULL) { fprintf(stderr,"\nCannot write: %s\n\n",OUTFILE); return 1; } fprintf(stderr,"Writing %s as Apple %s formatted %s.\n\n",OUTFILE,modeltypes[model],filetypes[outputtype]); } else fprintf(stderr,"Writing %s as Apple %s formatted %s.\n\n","STDOUT",modeltypes[model],filetypes[outputtype]); if(outputtype == MONITOR) { int i, j, saddr; unsigned long cmp_len; unsigned char *cmp_data; for(i=0;i> 8; ram[0xBE80 - (0x823 - 0x80C) + j++] = endj & 0xFF; ram[0xBE80 - (0x823 - 0x80C) + j++] = endj >> 8; ram[0x00] = 0xFF; ram[0xBF09] = 0x00; //BRK reset6502(); exec6502(0xBEE3); if(ram[0x00] != 0) fprintf(stderr,"WARNING: simulated checksum failed: %02X\n",ram[0x00]); inflate_time += clockticks6502/1023000.0; } //zero page src ram[0x0] = (0xBA00 - cmp_len) & 0xFF; ram[0x1] = (0xBA00 - cmp_len) >> 8; //zero page dst ram[0x2] = (segments[0].start) & 0xFF; ram[0x3] = (segments[0].start) >> 8; //setup JSR ram[0xBF00] = 0x20; // JSR $9B00 ram[0xBF01] = 0x00; ram[0xBF02] = 0xBA; ram[0xBF03] = 0x00; //BRK to stop simulation //run it reset6502(); exec6502(0xBF00); //compare (just to be safe) for(j=0;j 384) { segments[0].filename[strlen(segments[0].filename) - (length - sizeof(basic)/sizeof(char) - move_len - 384)] = '\0'; fprintf(stderr,"WARNING: BF00 page overflow: truncating display filename to %s\n\n",segments[0].filename); length = 384 + sizeof(basic)/sizeof(char) + move_len; } } else { if(length - sizeof(basic)/sizeof(char) - move_len > 256) { segments[0].filename[strlen(segments[0].filename) - (length - sizeof(basic)/sizeof(char) - move_len - 256)] = '\0'; fprintf(stderr,"WARNING: BF00 page overflow: truncating display filename to %s\n\n",segments[0].filename); length = 256 + sizeof(basic)/sizeof(char) + move_len; } } freq0 = 2000; freq1 = 1000; checksum = 0xff; if(basicload) { // write basic stub header[0] = length & 0xFF; header[1] = length >> 8; for(i=0;i<3;i++) { WRITEBYTE(header[i]); checksum ^= header[i]; } WRITEBYTE(checksum); appendtone(&output,&outputlength,1000,rate,0,1,&offset); appendtone(&output,&outputlength,770,rate,4.0,0,&offset); appendtone(&output,&outputlength,2500,rate,0,0.5,&offset); appendtone(&output,&outputlength,2000,rate,0,0.5,&offset); // write out basic program checksum = 0xff; for(i=0;i> 8; //load end table[2] = (cmp_start + segments[0].length + sizeof(inflatecode)/sizeof(char) + 1) & 0xff; table[3] = (cmp_start + segments[0].length + sizeof(inflatecode)/sizeof(char) + 1) >> 8; //inflate src table[4] = cmp_start & 0xff; table[5] = cmp_start >> 8; //inflate end table[8] = (segments[0].start + segments[0].codelength) & 0xff; table[9] = (segments[0].start + segments[0].codelength) >> 8; } else { //load start table[0] = segments[0].start & 0xff; table[1] = segments[0].start >> 8; //load end table[2] = (segments[0].start + segments[0].length + 1) & 0xff; table[3] = (segments[0].start + segments[0].length + 1) >> 8; } //JMP to code, inflate dst table[6] = segments[0].start & 0xff; table[7] = segments[0].start >> 8; table[10] = compress; table[11] = warm; if(fast) for(i=0;i> 8; freq0 = 2000; freq1 = 1000; for(i=0;i<3;i++) { WRITEBYTE(header[i]); checksum ^= header[i]; } WRITEBYTE(checksum); appendtone(&output,&outputlength,1000,rate,0,1,&offset); appendtone(&output,&outputlength,770,rate,4.0,0,&offset); appendtone(&output,&outputlength,2500,rate,0,0.5,&offset); appendtone(&output,&outputlength,2000,rate,0,0.5,&offset); // write out basic program checksum = 0xff; for(i=0;i> 8; ram[0x02] = 0x00; ram[0x03] = 0x90; ram[0x04] = 0xFF; ram[0x9089] = 0x85; //STA ram[0x908A] = 0x04; //zero page $04 ram[0x908B] = 0x00; //BRK reset6502(); exec6502(0x9065); if(ram[0x04] != 0) fprintf(stderr,"WARNING: simulated checksum failed: %02X\n",ram[0x04]); inflate_times[i] += clockticks6502/1023000.0; } //zero page src ram[0x10] = (0x8FFF - cmp_len) & 0xFF; ram[0x11] = (0x8FFF - cmp_len) >> 8; //zero page dst ram[0x12] = 0x00; ram[0x13] = 0x10; //setup JSR ram[0x9000] = 0x20; // JSR $9B00 ram[0x9001] = 0x00; ram[0x9002] = 0x9B; ram[0x9003] = 0x00; //BRK to stop simulation //run it reset6502(); exec6502(0x9000); //compare (just to be safe) err=0; for(j=0;j<7 * 4096;j++) if(ram[0x1000 + j] != segments[i].data[j]) { err = 1; break; } if(err) fprintf(stderr,"WARNING: simulated inflate failed at %04X\n",j+0x1000); inflate_times[i] += clockticks6502/1023000.0; free(segments[i].data); segments[i].data = cmp_data; orig_len = segments[i].length; segments[i].length = cmp_len; segments[i].start = 0x8FFF - segments[i].length; // compress ? // need to see what is faster, defaulting to compress for now // if not compressed do not set start location, change asm code to check for 0,0 // and not use inflate code // where to load data start_table[start_table_len++] = segments[i].start & 0xFF; start_table[start_table_len++] = segments[i].start >> 8; ones = zeros = 0; for(j=0;j=0;j--) { checksum = 0xff; WRITEBYTE(j/10 + 48 + 0x80); checksum ^= (j/10 + 48 + 0x80); WRITEBYTE(j%10 + 48 + 0x80); checksum ^= (j%10 + 48 + 0x80); WRITEBYTE(0x00); checksum ^= 0x00; WRITEBYTE(checksum); appendtone(&output,&outputlength,2000,rate,0,1,&offset); appendtone(&output,&outputlength,6000,rate,1,0,&offset); } */ if(k8) appendtone(&output,&outputlength,2000,rate,j,0,&offset); else appendtone(&output,&outputlength,6000,rate,j,0,&offset); checksum = 0xff; for(j=0;j grow) { grow = *length + n + 10000000; if((tmp = (double *)realloc(*sound, (grow) * sizeof(double))) == NULL) abort(); *sound = tmp; } //tmp -> (*sound) if(square) { int j; if(freq) for (i = 0; i < n; i++) { for(j = 0;j < rate / freq / 2;j++) (*sound)[*length + i++] = 1; for(j = 0;j < rate / freq / 2;j++) (*sound)[*length + i++] = -1; i--; } else for (i = 0; i < n; i++) (*sound)[*length + i] = 0; } else for(i=0;i=0;i--) { if(filename[i] == '.') break; stack[sp++] = filename[i]; } stack[sp] = '\0'; if(sp == strlen(filename) || sp == 0) return(NULL); if((rval = (char *)malloc(sp * sizeof(char))) == NULL) ; //do error code rval[sp] = '\0'; for(i=0;i> 24, fptr); fputc((totalsize & 0x00ff0000) >> 16, fptr); fputc((totalsize & 0x0000ff00) >> 8, fptr); fputc((totalsize & 0x000000ff), fptr); fprintf(fptr, "AIFF"); // Write the common chunk fprintf(fptr, "COMM"); fputc(0, fptr); // Size fputc(0, fptr); fputc(0, fptr); fputc(18, fptr); fputc(0, fptr); // Channels = 1 fputc(1, fptr); fputc((nsamples & 0xff000000) >> 24, fptr); // Samples fputc((nsamples & 0x00ff0000) >> 16, fptr); fputc((nsamples & 0x0000ff00) >> 8, fptr); fputc((nsamples & 0x000000ff), fptr); fputc(0, fptr); // Size = 16 fputc(bits, fptr); ConvertToIeeeExtended(nfreq, bit80); for (i = 0; i < 10; i++) fputc(bit80[i], fptr); // Write the sound data chunk fprintf(fptr, "SSND"); fputc((((bits / 8) * nsamples + 8) & 0xff000000) >> 24, fptr); // Size fputc((((bits / 8) * nsamples + 8) & 0x00ff0000) >> 16, fptr); fputc((((bits / 8) * nsamples + 8) & 0x0000ff00) >> 8, fptr); fputc((((bits / 8) * nsamples + 8) & 0x000000ff), fptr); fputc(0, fptr); // Offset fputc(0, fptr); fputc(0, fptr); fputc(0, fptr); fputc(0, fptr); // Block fputc(0, fptr); fputc(0, fptr); fputc(0, fptr); // Find the range themin = samples[0]; themax = themin; for (i = 1; i < nsamples; i++) { if (samples[i] > themax) themax = samples[i]; if (samples[i] < themin) themin = samples[i]; } if (themin >= themax) { themin -= 1; themax += 1; } themid = (themin + themax) / 2; themin -= themid; themax -= themid; if (ABS(themin) > ABS(themax)) themax = ABS(themin); // scale = amp * 32760 / (themax); scale = amp * ((bits == 16) ? 32760 : 124) / (themax); // Write the data for (i = 0; i < nsamples; i++) { if (bits == 16) { v = (unsigned short) (scale * (samples[i] - themid)); fputc((v & 0xff00) >> 8, fptr); fputc((v & 0x00ff), fptr); } else { v = (unsigned char) (scale * (samples[i] - themid)); fputc(v, fptr); } } } /* Write an WAVE sound file Only do one channel, only support 16 bit. Supports any (reasonable) sample frequency Little/big endian independent! */ // egan: changed code to support 8 bit. void Write_WAVE(FILE * fptr, double *samples, long nsamples, int nfreq, int bits, double amp) { unsigned short v; int i; unsigned long totalsize, bytespersec; double themin, themax, scale, themid; // Write the form chunk fprintf(fptr, "RIFF"); totalsize = (bits / 8) * nsamples + 36; fputc((totalsize & 0x000000ff), fptr); // File size fputc((totalsize & 0x0000ff00) >> 8, fptr); fputc((totalsize & 0x00ff0000) >> 16, fptr); fputc((totalsize & 0xff000000) >> 24, fptr); fprintf(fptr, "WAVE"); fprintf(fptr, "fmt "); // fmt_ chunk fputc(16, fptr); // Chunk size fputc(0, fptr); fputc(0, fptr); fputc(0, fptr); fputc(1, fptr); // Format tag - uncompressed fputc(0, fptr); fputc(1, fptr); // Channels fputc(0, fptr); fputc((nfreq & 0x000000ff), fptr); // Sample frequency (Hz) fputc((nfreq & 0x0000ff00) >> 8, fptr); fputc((nfreq & 0x00ff0000) >> 16, fptr); fputc((nfreq & 0xff000000) >> 24, fptr); bytespersec = (bits / 8) * nfreq; fputc((bytespersec & 0x000000ff), fptr); // Average bytes per second fputc((bytespersec & 0x0000ff00) >> 8, fptr); fputc((bytespersec & 0x00ff0000) >> 16, fptr); fputc((bytespersec & 0xff000000) >> 24, fptr); fputc((bits / 8), fptr); // Block alignment fputc(0, fptr); fputc(bits, fptr); // Bits per sample fputc(0, fptr); fprintf(fptr, "data"); totalsize = (bits / 8) * nsamples; fputc((totalsize & 0x000000ff), fptr); // Data size fputc((totalsize & 0x0000ff00) >> 8, fptr); fputc((totalsize & 0x00ff0000) >> 16, fptr); fputc((totalsize & 0xff000000) >> 24, fptr); // Find the range themin = samples[0]; themax = themin; for (i = 1; i < nsamples; i++) { if (samples[i] > themax) themax = samples[i]; if (samples[i] < themin) themin = samples[i]; } if (themin >= themax) { themin -= 1; themax += 1; } themid = (themin + themax) / 2; themin -= themid; themax -= themid; if (ABS(themin) > ABS(themax)) themax = ABS(themin); // scale = amp * 32760 / (themax); scale = amp * ((bits == 16) ? 32760 : 124) / (themax); // Write the data for (i = 0; i < nsamples; i++) { if (bits == 16) { v = (unsigned short) (scale * (samples[i] - themid)); fputc((v & 0x00ff), fptr); fputc((v & 0xff00) >> 8, fptr); } else { v = (unsigned char) (scale * (samples[i] - themid)); fputc(v + 0x80, fptr); } } } /* * C O N V E R T T O I E E E E X T E N D E D */ /* Copyright (C) 1988-1991 Apple Computer, Inc. * All rights reserved. * * Machine-independent I/O routines for IEEE floating-point numbers. * * NaN's and infinities are converted to HUGE_VAL or HUGE, which * happens to be infinity on IEEE machines. Unfortunately, it is * impossible to preserve NaN's in a machine-independent way. * Infinities are, however, preserved on IEEE machines. * * These routines have been tested on the following machines: * Apple Macintosh, MPW 3.1 C compiler * Apple Macintosh, THINK C compiler * Silicon Graphics IRIS, MIPS compiler * Cray X/MP and Y/MP * Digital Equipment VAX * * * Implemented by Malcolm Slaney and Ken Turkowski. * * Malcolm Slaney contributions during 1988-1990 include big- and little- * endian file I/O, conversion to and from Motorola's extended 80-bit * floating-point format, and conversions to and from IEEE single- * precision floating-point format. * * In 1991, Ken Turkowski implemented the conversions to and from * IEEE double-precision format, added more precision to the extended * conversions, and accommodated conversions involving +/- infinity, * NaN's, and denormalized numbers. */ #ifndef HUGE_VAL #define HUGE_VAL HUGE #endif /*HUGE_VAL */ #define FloatToUnsigned(f) ((unsigned long)(((long)(f - 2147483648.0)) + 2147483647L) + 1) void ConvertToIeeeExtended(double num, unsigned char *bytes) { int sign; int expon; double fMant, fsMant; unsigned long hiMant, loMant; if (num < 0) { sign = 0x8000; num *= -1; } else { sign = 0; } if (num == 0) { expon = 0; hiMant = 0; loMant = 0; } else { fMant = frexp(num, &expon); if ((expon > 16384) || !(fMant < 1)) { /* Infinity or NaN */ expon = sign | 0x7FFF; hiMant = 0; loMant = 0; /* infinity */ } else { /* Finite */ expon += 16382; if (expon < 0) { /* denormalized */ fMant = ldexp(fMant, expon); expon = 0; } expon |= sign; fMant = ldexp(fMant, 32); fsMant = floor(fMant); hiMant = FloatToUnsigned(fsMant); fMant = ldexp(fMant - fsMant, 32); fsMant = floor(fMant); loMant = FloatToUnsigned(fsMant); } } bytes[0] = expon >> 8; bytes[1] = expon; bytes[2] = hiMant >> 24; bytes[3] = hiMant >> 16; bytes[4] = hiMant >> 8; bytes[5] = hiMant; bytes[6] = loMant >> 24; bytes[7] = loMant >> 16; bytes[8] = loMant >> 8; bytes[9] = loMant; } uint8_t read6502(uint16_t address) { return ram[address]; } void write6502(uint16_t address, uint8_t value) { ram[address] = value; }