diff --git a/tools/tzx2wav/sources/Makefile b/tools/tzx2wav/sources/Makefile new file mode 100644 index 0000000..769d8bc --- /dev/null +++ b/tools/tzx2wav/sources/Makefile @@ -0,0 +1,4 @@ +APP=tzx2wav + +../$(APP): $(APP).c hardware.h + gcc -o $@ $< -lz \ No newline at end of file diff --git a/tools/tzx2wav/sources/hardware.h b/tools/tzx2wav/sources/hardware.h new file mode 100644 index 0000000..c6758a8 --- /dev/null +++ b/tools/tzx2wav/sources/hardware.h @@ -0,0 +1,224 @@ +///////////////////////////////////////////////////////////////////// +// TZX to VAV Converter v0.2 for Bloodshed Dev-C++ compiler // +// (C) 2006 Francisco Javier Crespo // +// // +// Hardware description header file // +///////////////////////////////////////////////////////////////////// + +// Hardware Type entries + +const char hwtype_01[] = "Computer"; +const char hwtype_02[] = "External Storage"; +const char hwtype_03[] = "ROM/RAM Type Add-On"; +const char hwtype_04[] = "Sound Device"; +const char hwtype_05[] = "Joystick"; +const char hwtype_06[] = "Mouse"; +const char hwtype_07[] = "Other Controller"; +const char hwtype_08[] = "Serial Port"; +const char hwtype_09[] = "Parallel Port"; +const char hwtype_10[] = "Printer"; +const char hwtype_11[] = "Modem"; +const char hwtype_12[] = "Digitiser"; +const char hwtype_13[] = "Network Adapter"; +const char hwtype_14[] = "Keyboard or Keypad"; +const char hwtype_15[] = "AD/DA Converter"; +const char hwtype_16[] = "EPROM Programmer"; + +// Computer entries + +const char hwid_01_01[] = "ZX Spectrum 16k"; +const char hwid_01_02[] = "ZX Spectrum 48k, Plus"; +const char hwid_01_03[] = "ZX Spectrum 48k Issue 1"; +const char hwid_01_04[] = "ZX Spectrum 128k (Sinclair)"; +const char hwid_01_05[] = "ZX Spectrum 128k +2 (Grey case)"; +const char hwid_01_06[] = "ZX Spectrum 128k +2A, +3"; +const char hwid_01_07[] = "Timex Sinclair TC-2048"; +const char hwid_01_08[] = "Timex Sinclair TS-2068"; +const char hwid_01_09[] = "Pentagon 128"; +const char hwid_01_10[] = "Sam Coupe"; +const char hwid_01_11[] = "Didaktik M"; +const char hwid_01_12[] = "Didaktik Gama"; +const char hwid_01_13[] = "ZX-81 with 1k RAM"; +const char hwid_01_14[] = "ZX-81 with 16k RAM or more"; +const char hwid_01_15[] = "ZX Spectrum 128k, Spanish version"; +const char hwid_01_16[] = "ZX Spectrum, Arabic version"; +const char hwid_01_17[] = "TK 90-X"; +const char hwid_01_18[] = "TK 95"; +const char hwid_01_19[] = "Byte"; +const char hwid_01_20[] = "Elwro"; +const char hwid_01_21[] = "ZS Scorpion"; +const char hwid_01_22[] = "Amstrad CPC 464"; +const char hwid_01_23[] = "Amstrad CPC 664"; +const char hwid_01_24[] = "Amstrad CPC 6128"; +const char hwid_01_25[] = "Amstrad CPC 464+"; +const char hwid_01_26[] = "Amstrad CPC 6128+"; +const char hwid_01_27[] = "Jupiter ACE"; +const char hwid_01_28[] = "Enterprise"; +const char hwid_01_29[] = "Commodore 64"; +const char hwid_01_30[] = "Commodore 128"; + +const char *hwids_01[30] = +{hwid_01_01, hwid_01_02, hwid_01_03, hwid_01_04, hwid_01_05, hwid_01_06, + hwid_01_07, hwid_01_08, hwid_01_09, hwid_01_10, hwid_01_11, hwid_01_12, + hwid_01_13, hwid_01_14, hwid_01_15, hwid_01_16, hwid_01_17, hwid_01_18, + hwid_01_19, hwid_01_20, hwid_01_21, hwid_01_22, hwid_01_23, hwid_01_24, + hwid_01_25, hwid_01_26, hwid_01_27, hwid_01_28, hwid_01_29, hwid_01_30}; + +// External Storage entries + +const char hwid_02_01[] = "Microdrive"; +const char hwid_02_02[] = "Opus Discovery"; +const char hwid_02_03[] = "Disciple"; +const char hwid_02_04[] = "Plus-D"; +const char hwid_02_05[] = "Rotronics Wafadrive"; +const char hwid_02_06[] = "TR-DOS (BetaDisk)"; +const char hwid_02_07[] = "Byte Drive"; +const char hwid_02_08[] = "Watsford"; +const char hwid_02_09[] = "FIZ"; +const char hwid_02_10[] = "Radofin"; +const char hwid_02_11[] = "Didaktik disk drives"; +const char hwid_02_12[] = "BS-DOS (MB-02)"; +const char hwid_02_13[] = "ZX Spectrum +3 disk drive"; +const char hwid_02_14[] = "JLO (Oliger) disk interface"; +const char hwid_02_15[] = "FDD3000"; +const char hwid_02_16[] = "Zebra disk drive"; +const char hwid_02_17[] = "Ramex Millenia"; +const char hwid_02_18[] = "Larken"; + +const char *hwids_02[18] = +{hwid_02_01, hwid_02_02, hwid_02_03, hwid_02_04, hwid_02_05, hwid_02_06, + hwid_02_07, hwid_02_08, hwid_02_09, hwid_02_10, hwid_02_11, hwid_02_12, + hwid_02_13, hwid_02_14, hwid_02_15, hwid_02_16, hwid_02_17, hwid_02_18}; + +// ROM/RAM Type Add-On entries + +const char hwid_03_01[] = "Sam Ram"; +const char hwid_03_02[] = "Multiface"; +const char hwid_03_03[] = "Multiface 128k"; +const char hwid_03_04[] = "Multiface +3"; +const char hwid_03_05[] = "MultiPrint"; +const char hwid_03_06[] = "MB-02 ROM/RAM expansion"; + +const char *hwids_03[6] = +{hwid_03_01, hwid_03_02, hwid_03_03, hwid_03_04, hwid_03_05, hwid_03_06}; + +// Sound Device entries + +const char hwid_04_01[] = "Classic AY hardware (compatible with 128k ZXs)"; +const char hwid_04_02[] = "Fuller Box AY sound hardware"; +const char hwid_04_03[] = "Currah microSpeech"; +const char hwid_04_04[] = "SpecDrum"; +const char hwid_04_05[] = "AY ACB stereo; Melodik"; +const char hwid_04_06[] = "AY ABC stereo"; + +const char *hwids_04[6] = +{hwid_04_01, hwid_04_02, hwid_04_03, hwid_04_04, hwid_04_05, hwid_04_06}; + +// Joystick entries + +const char hwid_05_01[] = "Kempston"; +const char hwid_05_02[] = "Cursor, Protek, AGF"; +const char hwid_05_03[] = "Sinclair 2 Left"; +const char hwid_05_04[] = "Sinclair 1 Right"; +const char hwid_05_05[] = "Fuller"; + +const char *hwids_05[5] = +{hwid_05_01, hwid_05_02, hwid_05_03, hwid_05_04, hwid_05_05}; + +// Mouse entries + +const char hwid_06_01[] = "AMX Mouse"; +const char hwid_06_02[] = "Kempston mouse"; + +const char *hwids_06[2] = {hwid_06_01, hwid_06_02}; + +// Other Controller entries + +const char hwid_07_01[] = "Trickstick"; +const char hwid_07_02[] = "ZX Light Gun"; +const char hwid_07_03[] = "Zebra Graphics Tablet"; + +const char *hwids_07[3] = {hwid_07_01, hwid_07_02, hwid_07_03}; + +// Serial Port entries + +const char hwid_08_01[] = "ZX Interface 1"; +const char hwid_08_02[] = "ZX Spectrum 128k"; + +const char *hwids_08[2] = {hwid_08_01, hwid_08_02}; + +// Parallel Port entries + +const char hwid_09_01[] = "Kempston S"; +const char hwid_09_02[] = "Kempston E"; +const char hwid_09_03[] = "ZX Spectrum +3"; +const char hwid_09_04[] = "Tasman"; +const char hwid_09_05[] = "DK'Tronics"; +const char hwid_09_06[] = "Hilderbay"; +const char hwid_09_07[] = "INES Printerface"; +const char hwid_09_08[] = "ZX LPrint Interface 3"; +const char hwid_09_09[] = "MultiPrint"; +const char hwid_09_10[] = "Opus Discovery"; +const char hwid_09_11[] = "Standard 8255 chip"; + +const char *hwids_09[11] = +{hwid_09_01, hwid_09_02, hwid_09_03, hwid_09_04, hwid_09_05, hwid_09_06, + hwid_09_07, hwid_09_08, hwid_09_09, hwid_09_10, hwid_09_11}; + +// Printer entries + +const char hwid_10_01[] = "ZX Printer, Alphacom 32 & Compatibles"; +const char hwid_10_02[] = "Generic Printer"; +const char hwid_10_03[] = "EPSON Compatible"; + +const char *hwids_10[3] = {hwid_10_01, hwid_10_02, hwid_10_03}; + +// Modem entries + +const char hwid_11_01[] = "VTX 5000"; +const char hwid_11_02[] = "T/S 2050 or Westridge 2050"; + +const char *hwids_11[2] = {hwid_11_01, hwid_11_02}; + +// Digitiser entries + +const char hwid_12_01[] = "RD Digital Tracer"; +const char hwid_12_02[] = "DK'Tronics Light Pen"; +const char hwid_12_03[] = "British MicroGraph Pad"; + +const char *hwids_12[3] = {hwid_12_01, hwid_12_02, hwid_12_03}; + +// Network Adapter entries + +const char hwid_13_01[] = "ZX interface 1"; + +const char *hwids_13[1] = {hwid_13_01}; + +// Keyboard or Keypad entries + +const char hwid_14_01[] = "Keypad for ZX Spectrum 128k"; + +const char *hwids_14[1] = {hwid_14_01}; + +// AD/DA Converter entries + +const char hwid_15_01[] = "Harley Systems ADC 8.2"; +const char hwid_15_02[] = "Blackboard Electronics"; + +const char *hwids_15[2] = {hwid_15_01, hwid_15_02}; + +// EPROM Programmer entries + +const char hwid_16_01[] = "Orme Electronics"; + +const char *hwids_16[1] = {hwid_16_01}; + +// Variables used in main program + +const char *hwtypes[16] = +{hwtype_01, hwtype_02, hwtype_03, hwtype_04, hwtype_05, hwtype_06, hwtype_07, hwtype_08, + hwtype_09, hwtype_10, hwtype_11, hwtype_12, hwtype_13, hwtype_14, hwtype_15, hwtype_16}; + +const char **hwids[16] = +{hwids_01, hwids_02, hwids_03, hwids_04, hwids_05, hwids_06, hwids_07, hwids_08, + hwids_09, hwids_10, hwids_11, hwids_12, hwids_13, hwids_14, hwids_15, hwids_16}; diff --git a/tools/tzx2wav/sources/tzx2wav.c b/tools/tzx2wav/sources/tzx2wav.c new file mode 100644 index 0000000..c080d03 --- /dev/null +++ b/tools/tzx2wav/sources/tzx2wav.c @@ -0,0 +1,2416 @@ +///////////////////////////////////////////////////////////////////// +// TZX to VAV Converter v0.2 for Bloodshed Dev-C++ compiler // +// (C) 2005-2006 Francisco Javier Crespo // +// // +// Originally based on source code from these works: // +// PLAYTZX v0.60b for Watcom C compiler (C) 1997-2004 Tomaz Kac // +// PLAYTZX Unix v0.12b (C) 2003 Tero Turtiainen / Fredrick Meunier // +///////////////////////////////////////////////////////////////////// + +// List of things TO DO: +// - Friendly functions to deal with processor endianness +// (http://www.intel.com/design/intarch/papers/endian.pdf) +// - Reorganize old original DOS-oriented code +// - Obtain C64 TZX files to be able to check valid output +// - Introduce new TZX v1.20 WIP draft specifications +// - Suggest new TZX blocks for Single Pulse bits / Manchester Encoding +// - Take care of possible buffer overflow with malformed TZX files + +#include +#include +#include +#include +#include +#include +#include +#include "zlib.h" +#include "hardware.h" + +#ifndef O_BINARY +#define O_BINARY 0 +#endif + +const char *build= "20060225"; + +#define MAJREV 1 // Major revision of the format this program supports +#define MINREV 13 // Minor revision of the format this program supports + +// C64 Loader defines ... + +#define ROM_S_HALF 616 // ROM Loader SHORT Half Wave +#define ROM_M_HALF 896 // ROM Loader MEDIUM Half Wave +#define ROM_L_HALF 1176 // ROM Loader LONG Half Wave + +#define STT_0_HALF 426 // Standard Turbo Tape BIT 0 Half Wave +#define STT_1_HALF 596 // Standard Turbo Tape BIT 1 Half Wave + +// Other defines ... + +#define LOAMP 0x26 // Low Level Amplitude (-3 dB) +#define HIAMP 0xDA // High Level Amplitude (-3 dB) +#define SGNLOW 0 +#define SGNHIGH 1 +#define WAVBUFLEN 0x10000 // 64 KB memory buffer for WAV file +#define CSWBUFLEN 0x10000 // 64 KB memory buffer for CSW file +#define ZBUFLEN 0x04000 // 16 KB memory buffer for CSW ZLIB output + +typedef struct { + unsigned int ChunkID; + unsigned int ChunkSize; + unsigned int Format; + unsigned int fmtChunkID; + unsigned int fmtChunkSize; + unsigned short AudioFormat; + unsigned short NumChannels; + unsigned int SampleRate; + unsigned int ByteRate; + unsigned short BlockAlign; + unsigned short BitsPerSample; + unsigned int dataChunkID; + unsigned int dataChunkSize; +} WavHdr; + +WavHdr header; // WAV file header +unsigned char *wavbuf; // Buffer for WAV file +unsigned int wavpos; // Current position in WAV buffer +int stereo = 0; // Stereo flag for CDDA waves + +unsigned char *cswbuf; // Buffer for CSW file +unsigned int cswpos; // Current position in CSW buffer +z_stream zs; // ZLIB stream handler +unsigned char *zbuf; // Buffer for ZLIB output +int zflush; // Flush state for ZLIB +unsigned int cswpulses = 0; // Total number of pulses for CSW header +int csw = 0; // Create CSW file instead of WAV file + +unsigned int sgn; // Sign of the wave being played +int amp; // Amplitude of the current signal (to be deprecated) +unsigned int freq = 44100; // Default Sample Frequency +unsigned char ampmono[2]={0x26,0xDA}; // Amplitudes of mono wave +unsigned char ampstereo[2][2]={{0x62,0xA5},{0x9E,0x5A}}; // Amplitudes of stereo wave + +int prvi; +int n,m; +int num; +unsigned char *d; +int line=3; +int ifh; // Input File Handle +int ofh; // Output File Handle +unsigned int flen; // File Length +unsigned char *mem; // File in Memory +int pos; // Position in File +int curr; // Current block that is playing +int numblocks; // Total Num. of blocks +unsigned long oflen; // Length of output file +int block[2048]; // Array of Block starts +double cycle; // Frequency / 3500000 (Z80 clock) + +int cpc=0; // Amstrad CPC tape ? +int sam=0; // SAM Coupe tape ? + +int id; // Current Block ID +int pilot; // Len of Pilot signal (in hp's) +int sb_pilot; // Pilot pulse +int sb_sync1; // Sync first half-period (hp) +int sb_sync2; // Sync second +int sb_bit0; // Bit-0 +int sb_bit1; // Bit-1 +int sb_pulse; // Pulse in Sequence of pulses and direct recording block +int lastbyte; // How many bits are in last byte of data ? +int pause_ms; // Pause after current block (in milliseconds) +int skippause=0; // Overrides pause value in last TZX block + +int singlepulse; // Flag to activate single pulse waves +int manchester; // Flag to activate manchester encoded waves + +unsigned char *data; // Data to be played +int datalen; // Len of ^^^ +int datapos; // Position in ^^^ +int bitcount; // How many bits to play in current byte ? +int sb_bit; // should we play bit 0 or 1 ? +char databyte; // Current Byte to be replayed of the data +signed short jump; // Relative Jump +int not_rec; // Some blocks were not recognised ?? +int files=0; // Number of Files on the command line +char finp[255]; // Input File (First Command Line Option) +char fout[255]; // Output File (Second Command Line Option or First with .WAV) +char errstr[255]; // Error String +int starting=1; // starting block +int ending=0; // ending block + +int info=0; // if info=1 then show EXTENSIVE information + // info=2 then show ONE LINE of Info per block +int pages=0; // Waiting after each page of the info ? +int expand=0; // Expand Groups ? +int draw=1; // Local flag for outputing a line when in a group +int mode128=0; // Are we working in 128k mode ? (for Stop in 48k block) + +int nfreq=0; // Did we choose new frequency with /freq switch ? +char k; +int speed; +int x,last,lastlen; + +int loop_start=0; // Position of the last Loop Start block +int loop_count=0; // Counter of the Loop +int call_pos=0; // Position of the last Call Sequence block +int call_num=0; // Number of Calls in the last Call Sequence block +int call_cur=0; // Current Call to be made +int num_sel; // Number of Selections in the Select block +int jumparray[256]; // Array of all possible jumps in Select block + +int sb_bit0_f, sb_bit0_s, sb_bit1_f, sb_bit1_s, xortype, sb_finishbyte_f, + sb_finishbyte_s, sb_finishdata_f, sb_finishdata_s, num_lead_in, xorvalue; +int trailing, sb_trailing; +char lead_in_byte; +int endian; +char add_bit; + +int inv = 0; + +char tstr[255]; +char tstr2[255]; +char tstr3[255]; +char tstr4[255]; +char spdstr[255]; +char pstr[255]; + +int numt, nump, t2; + +///////////////////////////////////////////////// +// Garbage collector and error handling routines +///////////////////////////////////////////////// + +void GarbageCollector (void) +{ + if (zbuf != NULL) free(zbuf); + if (cswbuf != NULL) free(cswbuf); + if (wavbuf != NULL) free(wavbuf); + if (mem != NULL) free(mem); + + if (ofh != 0) close(ofh); + if (ifh != 0) close(ifh); +} + +void Error (char *errstr) +{ + GarbageCollector(); + printf("\n-- Error: %s\n",errstr); + exit(-1); +} + +/////////////////////////////// +// CSW v1.01 handling routines +/////////////////////////////// + +void CSW1_Init(void) +{ + // Official CSW format documentation at: + // http://www.ramsoft.bbk.org/csw.html + + unsigned short Revision = 0x0101; + unsigned char CompType = 1; + unsigned int Reserved = 0; + + if (freq > 65535) + Error("Sample rates > 65535 Hz cannot be used in CSW v1.01 format"); + + ofh = open(fout, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE); + if (ofh == -1) + Error("Output file could not be created"); + + cswbuf = (unsigned char *) malloc(CSWBUFLEN); + if (cswbuf == NULL) + { + Error("Not enough memory to set up CSW file buffer!"); + } + cswpos = 0; + + write(ofh,"Compressed Square Wave\032",23); + write(ofh,&Revision,2); // Major & Minor revision + write(ofh,&freq,2); // Sample Rate + write(ofh,&CompType,1); // Compression Type + write(ofh,&inv,1); // Polarity + write(ofh,&Reserved,3); // Reserved bytes +} + +void CSW1_Write (unsigned int samples) +{ + // I/O operations are more expensive than CPU operations. + // We are using a 64 KB memory buffer. + + if (samples < 256) + { + cswbuf[cswpos++] = samples; // Store number of samples + if (cswpos == CSWBUFLEN) + { + write(ofh,cswbuf,CSWBUFLEN); + cswpos = 0; + } + } + else + { + if ((cswpos+5) >= CSWBUFLEN) + { + write(ofh,cswbuf,cswpos); + cswpos = 0; + } + cswbuf[cswpos++] = 0; // Store signal for Little Endian integer + cswbuf[cswpos++] = (samples & 0x000000FF); + cswbuf[cswpos++] = (samples & 0x0000FF00) >> 8; + cswbuf[cswpos++] = (samples & 0x00FF0000) >> 16; + cswbuf[cswpos++] = (samples & 0xFF000000) >> 24; + } +} + +void CSW1_Stop(void) +{ + if (cswpos) + write(ofh,cswbuf,cswpos); + free(cswbuf); cswbuf = NULL; + oflen = lseek(ofh,0,SEEK_END); + close(ofh); ofh = 0; +} + +/////////////////////////////// +// CSW v2.00 handling routines +/////////////////////////////// + +void CSW2_Init(void) +{ + + int err; + unsigned short Revision = 2; + unsigned char CompType = 2; + unsigned char HeaderExt = 0; + char cswapp[16] = "TZX2WAV v0.2"; + + ofh = open(fout, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE); + if (ofh == -1) + Error("Output file could not be created"); + + cswbuf = (unsigned char *) malloc(CSWBUFLEN); + if (cswbuf == NULL) + { + Error("Not enough memory to set up CSW file buffer!"); + } + cswpos = 0; + + zbuf = (unsigned char *) malloc(ZBUFLEN); + if (zbuf == NULL) + { + Error("Not enough memory to set up CSW ZLIB buffer!"); + } + + zs.zalloc = Z_NULL; + zs.zfree = Z_NULL; + zs.opaque = Z_NULL; + err = deflateInit(&zs,9); + if (err != Z_OK) + { + Error("Error initializing CSW ZLIB buffer!"); + } + + zflush = Z_NO_FLUSH; + + write(ofh,"Compressed Square Wave\032",23); + write(ofh,&Revision,2); // Major & Minor revision + write(ofh,&freq,4); // Sample Rate + write(ofh,&cswpulses,4); // Total number of pulses + write(ofh,&CompType,1); // Compression Type + write(ofh,&inv,1); // Polarity + write(ofh,&HeaderExt,1); // Header extension bytes + write(ofh,cswapp,16); // Encoding Application +} + +void ZLIBWrite(unsigned char *source, int length) +{ + // Compresses source data into ZLIB buffer and writes to file if full + + int err; + unsigned int filled; + + zs.avail_in = length; + zs.next_in = source; + + do { + zs.avail_out = ZBUFLEN; + zs.next_out = zbuf; + err = deflate(&zs,zflush); + filled = ZBUFLEN - zs.avail_out; + write (ofh,zbuf,filled); + } while (zs.avail_out == 0); +} + +void CSW2_Write (unsigned int samples) +{ + // I/O operations are more expensive than CPU operations. + // We are using a 64 KB memory buffer. + + cswpulses++; + if (samples < 256) + { + cswbuf[cswpos++] = samples; // Store number of samples + if (cswpos == CSWBUFLEN) + { + ZLIBWrite(cswbuf,CSWBUFLEN); + cswpos = 0; + } + } + else + { + if ((cswpos+5) >= CSWBUFLEN) + { + ZLIBWrite(cswbuf,cswpos); + cswpos = 0; + } + cswbuf[cswpos++] = 0; // Store signal for Little Endian integer + cswbuf[cswpos++] = (samples & 0x000000FF); + cswbuf[cswpos++] = (samples & 0x0000FF00) >> 8; + cswbuf[cswpos++] = (samples & 0x00FF0000) >> 16; + cswbuf[cswpos++] = (samples & 0xFF000000) >> 24; + } +} + +void CSW2_Stop(void) +{ + // Flushes last ZLIB buffer and updates CSW file header + + int len; + int err; + int done = 0; + + if (cswpos) + { + zflush = Z_FINISH; + ZLIBWrite(cswbuf,cswpos); + } + + deflateEnd(&zs); + free(zbuf); zbuf = NULL; + free(cswbuf); cswbuf = NULL; + + lseek(ofh,0x1D,SEEK_SET); + write(ofh,&cswpulses,4); + oflen = lseek(ofh,0,SEEK_END); + close(ofh); ofh = 0; +} + +////////////////////////////// +// WAV file handling routines +////////////////////////////// + +void WAV_Init(void) +{ + // Nice WAV format info in this web site: + // http://ccrma.stanford.edu/courses/422/projects/WaveFormat/ + + ofh = open(fout, O_WRONLY | O_BINARY | O_CREAT | O_TRUNC, S_IREAD | S_IWRITE); + if (ofh == -1) + Error("Output file could not be created"); + + wavbuf = (unsigned char *) malloc(WAVBUFLEN); + if (wavbuf == NULL) + Error("Not enough memory to set up WAV file buffer!"); + + header.ChunkID = 0x46464952; // "RIFF" ID + header.ChunkSize = 40; + header.Format = 0x45564157; // "WAVE" ID + header.fmtChunkID = 0x20746D66; // "fmt " ID + header.fmtChunkSize = 16; + header.AudioFormat = 1; // PCM Linear Quantization + header.SampleRate = freq; + header.dataChunkID = 0x61746164; // "data" ID + header.dataChunkSize = 0; + + if (stereo) + { + header.NumChannels = 2; + header.BitsPerSample = 16; + header.ByteRate = freq * 4; + header.BlockAlign = 4; + } + else + { + header.NumChannels = 1; + header.BitsPerSample = 8; + header.ByteRate = freq; + header.BlockAlign = 1; + } + + write(ofh,&header,44); + wavpos = 0; +} + +void WAV_Write (unsigned int len) +{ + // I/O operations are more expensive than CPU operations. + // We are using a 64 KB memory buffer. + + if (amp==LOAMP) sgn=0; + else sgn=1; + + if (stereo) + { + header.dataChunkSize += (len * 4); + // 16-bit stereo samples are stored as signed integers + // First left channel, then right channel + while (len) + { + wavbuf[wavpos++] = ampstereo[sgn][0]; + wavbuf[wavpos++] = ampstereo[sgn][1]; + wavbuf[wavpos++] = ampstereo[sgn][0]; + wavbuf[wavpos++] = ampstereo[sgn][1]; + len--; + if (wavpos == WAVBUFLEN) + { + write(ofh,wavbuf,WAVBUFLEN); + wavpos = 0; + } + } + } + else + { + header.dataChunkSize += len; + // 8-bit mono samples are unsigned integers + while (len) + { + wavbuf[wavpos++] = ampmono[sgn]; + len--; + if (wavpos == WAVBUFLEN) + { + write(ofh,wavbuf,WAVBUFLEN); + wavpos = 0; + } + } + } +} + +void WAV_Stop(void) +{ + // Flushes last WAV memory buffer and updates WAV file header + + if (wavpos) + write(ofh,wavbuf,wavpos); + + lseek(ofh,0,SEEK_SET); + header.ChunkSize = header.dataChunkSize + 36; + write(ofh,&header,44); + oflen = lseek(ofh,0,SEEK_END); + free(wavbuf); wavbuf = NULL; + close(ofh); ofh = 0; +} + +////////////////////////////////// +// Generic wave handling routines +////////////////////////////////// + +unsigned int Samples (unsigned int n) +{ + // Convert a sampling value in Z80 T-States to samples for wave output + return ((unsigned int)(0.5 + (cycle*(double)n))); +} + +void ToggleAmp(void) +{ + // Toggles the sign of the wave + // WHOLE CONCEPT TO BE RECODED IN ToggleSgn(); + + if (amp==LOAMP) amp=HIAMP; + else amp=LOAMP; +} + +void PlayWave(unsigned int len) +{ + // Generate wave data for "len" samples. + + if (inv) // Reverse the signal if needed + { + if (amp == LOAMP) amp = HIAMP; + else amp = LOAMP; + } + + switch (csw) + { + case 1 : CSW1_Write(len); break; + case 2 : CSW2_Write(len); break; + default : WAV_Write(len); break; + } +} + +void PauseWave (unsigned int pause_ms) +{ + // Waits for "pause" milliseconds + + int p; + if ((!skippause)||(curr!=(numblocks-1))) + { + p = (unsigned int)((((float) pause_ms)*freq)/1000.0); + PlayWave(p); + } +} + +///////////////////////////// +// TZX Commodore 64 routines +///////////////////////////// + +void PlayC64(unsigned int len) +{ + PlayWave(len); + ToggleAmp(); + PlayWave(len); + ToggleAmp(); +} + +void PlayC64ROMByte(char byte, int finish) +{ +xorvalue=xortype; +while (bitcount) + { + if (!endian) sb_bit=byte&0x01; + else sb_bit=byte&0x80; + if (sb_bit) + { + if (sb_bit1_f) PlayC64(sb_bit1_f); + if (sb_bit1_s) PlayC64(sb_bit1_s); + xorvalue^=sb_bit; + } + else + { + if (sb_bit0_f) PlayC64(sb_bit0_f); + if (sb_bit0_s) PlayC64(sb_bit0_s); + xorvalue^=sb_bit; + } + if (!endian) byte>>=1; + else byte<<=1; + bitcount--; + } +if (xortype != 0xFF) + { + if (xorvalue) + { + if (sb_bit1_f) PlayC64(sb_bit1_f); + if (sb_bit1_s) PlayC64(sb_bit1_s); + } + else + { + if (sb_bit0_f) PlayC64(sb_bit0_f); + if (sb_bit0_s) PlayC64(sb_bit0_s); + } + } +if (!finish) + { + if (sb_finishbyte_f) PlayC64(sb_finishbyte_f); + if (sb_finishbyte_s) PlayC64(sb_finishbyte_s); + } +else + { + if (sb_finishdata_f) PlayC64(sb_finishdata_f); + if (sb_finishdata_s) PlayC64(sb_finishdata_s); + } +} + +void PlayC64TurboByte(char byte) +{ + int add_num; + + add_num = add_bit & 3; + + if (add_num && !(add_bit&4)) + { + while(add_num) + { + if (add_bit & 8) PlayC64(sb_bit1); + else PlayC64(sb_bit0); + add_num--; + } + } + + while (bitcount) + { + if (!endian) sb_bit=byte&0x01; + else sb_bit=byte&0x80; + if (sb_bit) PlayC64(sb_bit1); + else PlayC64(sb_bit0); + if (!endian) byte>>=1; + else byte<<=1; + bitcount--; + } + + if (add_num && (add_bit&4)) + { + while(add_num) + { + if (add_bit&8) PlayC64(sb_bit1); + else PlayC64(sb_bit0); + add_num--; + } + } +} + +//////////////////////////////// +// Game identification routines +//////////////////////////////// + +void GetC64ROMName(char *name, unsigned char *data) +{ + char d; + + for (n=0; n<16; n++) + { + d=data[14+n]; + if (d<32 || d>125) + name[n]=' '; + else + name[n]=d; + } + name[n]=0; +} + +void GetC64StandardTurboTapeName(char *name, unsigned char *data) +{ + char d; + + for (n=0; n<16; n++) + { + d=data[15+n]; + if (d<32 || d>125) + name[n]=' '; + else + name[n]=d; + } + name[n]=0; +} + +void IdentifyC64ROM(int pos, unsigned char *data, int type) +{ + char name[255]; + + // Determine Loader type + if ((sb_pilot == ROM_S_HALF) && (sb_sync1 == ROM_L_HALF) && (sb_sync2 == ROM_M_HALF) && + (sb_bit0_f == ROM_S_HALF) && (sb_bit0_s == ROM_M_HALF) && (sb_bit1_f == ROM_M_HALF) && + (sb_bit1_s == ROM_S_HALF) && (xortype == 0x01)) + { + // ROM Loader + if ((data[0]==0x89) && (data[1]==0x88) && (data[2]==0x87) && (data[3]==0x86) && + (data[4]==0x85) && (data[5]==0x84) && (data[6]==0x83) && (data[7]==0x82) && + (data[8]==0x81)) + { + if (pos==202) + { + if (!type) + { + strcpy(name,"Header: "); + GetC64ROMName(name+8, data); + } + else + { + strcpy(name,"ROM Header: "); + GetC64ROMName(name+12, data); + } + } + else + { + if (!type) + { + strcpy(name,"Data Block "); + } + else + { + strcpy(name,"ROM: Data Block"); + } + } + } + else + { + if (!type) strcpy(name,"------------------------"); + else strcpy(name,"ROM: Last Block Repeated"); + } + strcpy(tstr,name); + strcpy(spdstr,"C64 ROM Data "); + return; + } + + if (!type) strcpy(tstr,"------------------------"); + else strcpy(tstr,"Unknown"); + strcpy(spdstr,"C64 Data "); +} + +void IdentifyC64Turbo(int pos, unsigned char *data, int type) +{ + char name[255]; + + // Determine Loader type + if (sb_bit0 == STT_0_HALF && sb_bit1 == STT_1_HALF && lead_in_byte == 0x02) + { + // Standard Turbo Tape Loader + if (data[0]==0x09 && data[1]==0x08 && data[2]==0x07 && data[3]==0x06 && + data[4]==0x05 && data[5]==0x04 && data[6]==0x03 && data[7]==0x02 && + data[8]==0x01) + { + if (pos==32 && data[9] != 0x00) + { + if (!type) + { + strcpy(name,"Header: "); + GetC64StandardTurboTapeName(name+8, data); + } + else + { + strcpy(name,"TurboTape Header: "); + GetC64StandardTurboTapeName(name+18, data); + } + } + else + { + if (!type) strcpy(name,"------------------------"); + else strcpy(name,"TurboTape Data Block"); + } + } + else + { + if (!type) strcpy(name,"------------------------"); + else strcpy(name,"TurboTape Unknown"); + } + strcpy(tstr,name); + strcpy(spdstr,"C64 Turbo "); + return; + } + if (!type) strcpy(tstr,"------------------------"); + else strcpy(tstr,"Unknown"); + strcpy(spdstr,"C64 Data "); +} + +void Identify(int len, unsigned char *temp, int type) +{ + int n; + int s; + + if (cpc) + { + if (temp[0]==44) + { + if (!type) s=4; + else s=0; + strcpy(tstr," "); + for (n=0; n<16; n++) + { + if (temp[n+1]) tstr[n+s]=temp[n+1]; + else tstr[n+s]=' '; + } + for (n=0; n<4; n++) tstr[n+s+16]=' '; + tstr[n+s+16]=0; + } + else + { + if (!type) + strcpy(tstr," ------------------ "); + else + strcpy(tstr,"Headerless"); + } + return; + } + + if (sam) + { + if (temp[0]==1 && (len>80 && len<84) && (temp[1]>=0x10 && temp[1]<=0x13)) + { + if (!type) + { + s=14; + switch (temp[1]) + { + case 0x10: strcpy(tstr," Program : "); break; + case 0x11: strcpy(tstr," Num. Array : "); break; + case 0x12: strcpy(tstr,"Char. Array : "); break; + case 0x13: strcpy(tstr," Bytes : "); break; + } + } + else + { + switch (temp[1]) + { + case 0x10: strcpy(tstr,"Program : "); s=10; break; + case 0x11: strcpy(tstr,"Num. Array : "); s=13; break; + case 0x12: strcpy(tstr,"Char. Array : "); s=14; break; + case 0x13: strcpy(tstr,"Bytes : "); s=8; break; + } + } + for (n=0; n<10; n++) + { + if (temp[n+2]>31 && temp[n+2]<127) + tstr[n+s]=temp[n+2]; + else + tstr[n+s]=32; + } + tstr[n+s]=0; + } + else + { + if (!type) + strcpy(tstr," --------------------"); // Not Header + else + strcpy(tstr,"Headerless"); + } + return; + } + + if (temp[0]==0 && (len==19 || len==20) && temp[1]<4) + { + if (!type) + { + s=14; + switch (temp[1]) + { + case 0x00: strcpy(tstr," Program : "); break; + case 0x01: strcpy(tstr," Num. Array : "); break; + case 0x02: strcpy(tstr,"Char. Array : "); break; + case 0x03: strcpy(tstr," Bytes : "); break; + } + } + else + { + switch (temp[1]) + { + case 0x00: strcpy(tstr,"Program : "); s=10; break; + case 0x01: strcpy(tstr,"Num. Array : "); s=13; break; + case 0x02: strcpy(tstr,"Char. Array : "); s=14; break; + case 0x03: strcpy(tstr,"Bytes : "); s=8; break; + } + } + for (n=0; n<10; n++) + { + if (temp[n+2]>31 && temp[n+2]<127) + tstr[n+s]=temp[n+2]; + else + tstr[n+s]=32; + } + tstr[n+s]=0; + } + else + { + if (!type) + strcpy(tstr," --------------------"); // Not Header + else + strcpy(tstr,"Headerless"); + } +} + +////////////////////////////////////////////////////////// +// Conversion routines to fetch bytes in Big Endian order +////////////////////////////////////////////////////////// + +unsigned int Get2(unsigned char *pointer) +{ + return (pointer[0] | (pointer[1]<<8)); +} + +unsigned int Get3(unsigned char *pointer) +{ + return (pointer[0] | (pointer[1]<<8) | (pointer[2]<<16)); +} + +unsigned int Get4(unsigned char *pointer) +{ + return (pointer[0] | (pointer[1]<<8) | (pointer[2]<<16) | (pointer[3]<<24)); +} + +///////////////////////// +// Miscelaneous routines +///////////////////////// + +char MirrorByte(char s) +{ + return((s<<7)+((s<<5)&64)+((s<<3)&32)+((s<<1)&16)+((s>>1)&8)+((s>>3)&4)+((s>>5)&2)+(s>>7)); +} + +int getnumber(char *s) +{ + // Returns the INT number contained in string *s + + int i; + sscanf(s,"%d",&i); return(i); +} + +void ChangeFileExtension(char *str,const char *ext) +{ + // Changes the File Extension of String *str to *ext + + int n; + n=strlen(str); + while (str[n]!='.') n--; + n++; + str[n]=0; + strcat(str,ext); +} + +void invalidoption(char *s) +{ + // Prints the Invalid Option error + + sprintf(errstr,"Invalid Option %s !",s); + Error(errstr); +} + +char * GetCheckSum(unsigned char *data, unsigned int len) +{ + // Calculates a XOR checksum for a block and returns a STRING containing the result + + unsigned int n; + unsigned char csum = 0; + + for (n=0; n21) + { + printf("scroll?\n"); + k=getchar(); + if (k==27) Error("ESCAPE key pressed!"); + if (!k) getchar(); + printf("\n"); + line=0; + } + } + printf(s); +} + +int MultiLine(char *s, int spaces, char *d) +{ + // This will convert a text which has lines separated by a single LF (13 dec) + // character to the text that can be outputed by MSDOS (LF NL) ... it will also + // Add a number of spaces to the beginning of each line (EXCEPT the first one - + // so you can use the Description: Text stuff ;) ) + // NOTE: Some UNIX system like LINUX can cope with just LF char (13), some + // other systems will need just CR char (10) ... experiment :) + + int n=0; + int m=0; + int i; + int l=0; + + while (s[n]) + { + if (s[n]==13) + { + d[m]=13; + d[m+1]=10; // Here is the MS-DOS output for line-end + m+=2; + for (i=0; i 44100 + pause_ms=Get2(&data[2]); // (Should work for frequencies upto 48000) + lastbyte=(int) data[4]; + datalen=Get3(&data[5]); + if (info!=1) + { + if (draw) printf(" Direct Recording Length:%6d Original Freq.: %5d Hz\n", + datalen, (int) (0.5+(3500000.0/ (double) Get2(&data[0])))); + if (info!=2) + { + data=&data[8]; + datapos=0; + // Replay Direct Recording block ... + while (datalen) + { + if (datalen!=1) bitcount=8; + else bitcount=lastbyte; + databyte=data[datapos]; + while (bitcount) + { + if (databyte&0x80) amp=HIAMP; + else amp=LOAMP; + PlayWave(sb_pulse); + databyte<<=1; + bitcount--; + } + datalen--; + datapos++; + } + ToggleAmp(); // Changed on 26-01-2005 + if (pause_ms) PauseWave(pause_ms); + } + } + else + { + sprintf(tstr,"Block %03d (%05X): 15 - Direct Recording\n",curr+1,block[curr]+10); writeout(tstr); + sprintf(tstr," Length:%6d bytes\n",datalen); writeout(tstr); + sprintf(tstr," Original Frequency: %5d T-States/Sample (%5d Hz)\n", + Get2(data),(int) (0.5+(3500000.0/ (double) Get2(data)))); writeout(tstr); + sprintf(tstr," Last byte used: %5d samples\n",lastbyte); writeout(tstr); + sprintf(tstr," Pause after block: %5d milliseconds\n\n",pause_ms); line++; writeout(tstr); + } +} + +void Analyse_ID16 (void) // C64 ROM Type Data Block +{ + data+=4; + sb_pilot=Get2(&data[0]); + pilot=Get2(&data[2]); + sb_sync1=Get2(&data[4]); + sb_sync2=Get2(&data[6]); + sb_bit0_f=Get2(&data[8]); + sb_bit0_s=Get2(&data[10]); + sb_bit1_f=Get2(&data[12]); + sb_bit1_s=Get2(&data[14]); + xortype=(int)(data[16]); + sb_finishbyte_f=Get2(&data[17]); + sb_finishbyte_s=Get2(&data[19]); + sb_finishdata_f=Get2(&data[21]); + sb_finishdata_s=Get2(&data[23]); + sb_trailing=Get2(&data[25]); + trailing=Get2(&data[27]); + lastbyte=(int)(data[29]); + endian=data[30]; + pause_ms=Get2(&data[31]); + datalen=Get3(&data[33]); + data+=36; + IdentifyC64ROM(datalen, data, 1); + if (info==1) + { + sprintf(pstr,"Block %03d (%05X): 16 - C64 ROM Type Data - %s\n",curr+1,block[curr]+10,tstr); writeout(pstr); + sprintf(tstr," Length: %5d bytes\n",datalen); writeout(tstr); + sprintf(tstr," Flag: %5d ($%02X)\n",data[0],data[0]); writeout(tstr); + if (pilot) + { sprintf(tstr," Pilot pulse: %5d T-States, length: %5d pulses\n",sb_pilot,pilot); writeout(tstr); } + else + { sprintf(tstr," Pilot pulse: None\n"); writeout(tstr); } + sprintf(tstr," Sync pulses: %5d & %5d T-States\n",sb_sync1,sb_sync2); writeout(tstr); + sprintf(tstr," Bit-0 pulses: %5d & %5d T-States\n",sb_bit0_f,sb_bit0_s); writeout(tstr); + sprintf(tstr," Bit-1 pulses: %5d & %5d T-States\n",sb_bit1_f,sb_bit1_s); writeout(tstr); + sprintf(tstr," Last byte used: %5d bits\n",lastbyte); writeout(tstr); + if (xortype != 0xFF) + { sprintf(tstr," Byte XOR Type: %d XOR all bits\n",xortype); writeout(tstr); } + else + { sprintf(tstr," Byte XOR Type: None\n"); writeout(tstr); } + sprintf(tstr," Finish Byte pulses: %5d & %5d T-States\n",sb_finishbyte_f,sb_finishbyte_s); writeout(tstr); + sprintf(tstr," Finish Data pulses: %5d & %5d T-States\n",sb_finishdata_f,sb_finishdata_s); writeout(tstr); + if (trailing) + { sprintf(tstr," Trailing Tone pulse: %5d T-States, length: %5d pulses\n",sb_trailing,trailing); writeout(tstr); } + else + { sprintf(tstr," Trailing Tone pulse: None\n"); writeout(tstr); } + if (endian) + strcpy(pstr, "MSb"); + else + strcpy(pstr, "LSb"); + sprintf(tstr," Endianess: %s\n",pstr); writeout(tstr); + sprintf(tstr," Pause after block: %5d milliseconds\n\n",pause_ms); line++; writeout(tstr); + sprintf(tstr," First: %02X , Last: %02X, Len: %d\n\n",data[0], data[datalen-1], datalen); line++; writeout(tstr); + } +} + +void Analyse_ID17 (void) // C64 Turbo Tape Data Block +{ + data+=4; + sb_bit0=Get2(&data[0]); + sb_bit1=Get2(&data[2]); + add_bit=data[4]; + num_lead_in=Get2(&data[5]); + lead_in_byte=data[7]; + lastbyte=(int) data[8]; + endian=data[9]; + trailing=Get2(&data[10]); + sb_trailing=data[12]; + pause_ms=Get2(&data[13]); + datalen=Get3(&data[15]); + data+=18; + IdentifyC64Turbo(datalen, data, 1); + if (info==1) + { + sprintf(pstr,"Block %03d (%05X): 17 - C64 Turbo Tape Type Data - %s\n",curr+1,block[curr]+10,tstr); writeout(pstr); + sprintf(tstr," Length: %5d bytes\n",datalen); writeout(tstr); + sprintf(tstr," Flag: %5d ($%02X)\n",data[0],data[0]); writeout(tstr); + if (num_lead_in) + { sprintf(tstr," Lead In Bytes: %5d, Value: %3d ($%02X)\n",num_lead_in,lead_in_byte,lead_in_byte); writeout(tstr); } + else + { sprintf(tstr," Lead In Bytes: None\n"); writeout(tstr); } + sprintf(tstr," Bit-0 pulse: %5d T-States\n",sb_bit0); writeout(tstr); + sprintf(tstr," Bit-1 pulse: %5d T-States\n",sb_bit1); writeout(tstr); + if (add_bit&3) + { + if (add_bit&4) + strcpy(pstr, "After"); + else + strcpy(pstr, "Before"); + sprintf(tstr," Additional Bits: %5d %s Byte, Value %1d\n",add_bit&3, pstr, (add_bit>>3)&1); writeout(tstr); + } + else + { sprintf(tstr," Additional Bits: None\n"); writeout(tstr); } + + sprintf(tstr," Last byte used: %5d bits\n",lastbyte); writeout(tstr); + if (endian) + strcpy(pstr, "MSb"); + else + strcpy(pstr, "LSb"); + sprintf(tstr," Endianess: %s\n",pstr); writeout(tstr); + if (trailing) + { sprintf(tstr," Trailing Bytes: %5d, Value: %3d ($%02X)\n",trailing,sb_trailing,sb_trailing); writeout(tstr); } + else + { sprintf(tstr," Trailing Bytes: None\n"); writeout(tstr); } + sprintf(tstr," Pause after block: %5d milliseconds\n\n",pause_ms); line++; writeout(tstr); + } +} + +void Analyse_ID20 (void) // Pause or Stop the Tape command +{ + pause_ms=Get2(&data[0]); + amp=LOAMP; + if (pause_ms) + { + if (info!=1) + { + if (draw) printf(" Pause Length: %2.3fs\n",((float) pause_ms)/1000.0); + if (info!=2) + { + PauseWave(pause_ms); + amp=LOAMP; + } + } + else + { + sprintf(tstr,"Block %03d (%05X): 20 - Pause (Silence)\n",curr+1,block[curr]+10); + writeout(tstr); + sprintf(tstr," Duration: %5d milliseconds\n\n",pause_ms); + line++; + writeout(tstr); + } + } + else + { + if (info!=1) + { + if (draw) printf(" Stop the tape command!\n"); + if (info!=2) + { + PauseWave(5000); // 5 seconds of pause in "Stop Tape" wave output + amp=LOAMP; + } + } + else + { + sprintf(tstr,"Block %03d (%05X): 20 - Stop the Tape Command\n\n",curr+1,block[curr]+10); + line++; + writeout(tstr); + } + } +} + +void Analyse_ID21 (void) // Group Start +{ + CopyString(pstr,&data[1],data[0]); + if (info!=1) + { + if (draw) printf(" Group: %s\n",pstr); + } + else + { + sprintf(tstr,"Block %03d (%05X): 21 - Group: %s\n\n",curr+1,block[curr]+10, pstr); + line++; + writeout(tstr); + } + if (!expand) draw=0; +} + +void Analyse_ID22 (void) // Group End +{ + if (info!=1) + { + if (draw) printf(" Group End\n"); + } + else + { + sprintf(tstr,"Block %03d (%05X): 22 - Group End\n\n",curr+1,block[curr]+10); + line++; + writeout(tstr); + } + draw=1; +} + +void Analyse_ID23 (void) // Jump To Relative +{ + jump = (signed short)(data[0]+data[1]*256); + if (info!=1) + { + if (draw) printf(" Jump Relative: %d (To Block %d)\n",jump,curr+jump+1); + if (!info) + { + curr+=jump; + curr--; + } + } + else + { + sprintf(tstr,"Block %03d (%05X): 23 - Jump Relative: %d (To Block %d)\n\n",curr+1,block[curr]+10, jump, curr+jump+1); + line++; + writeout(tstr); + } +} + +void Analyse_ID24 (void) // Loop Start +{ + loop_start=curr; + loop_count=Get2(&data[0]); + if (info!=1) + { + if (draw) printf(" Loop Start, Counter: %d\n",loop_count); + } + else + { + sprintf(tstr,"Block %03d (%05X): 24 - Loop Start, Counter: %d\n\n",curr+1,block[curr]+10, loop_count-1); + line++; + writeout(tstr); + } +} + +void Analyse_ID25 (void) // Loop End +{ + if (info!=1) + { + if (info!=2) + { + loop_count--; + if (loop_count>0) + { + if (draw) printf(" Loop End, Still To Go %d Time(s)!\n",loop_count); + curr=loop_start; + } + else + { + if (draw) printf(" Loop End, Finished\n"); + } + } + else + { + if (draw) printf(" Loop End\n"); + } + } + else + { + sprintf(tstr,"Block %03d (%05X): 25 - Loop End\n\n",curr+1,block[curr]+10); + line++; + writeout(tstr); + } +} + +void Analyse_ID26 (void) // Call Sequence +{ + call_pos=curr; + call_num=Get2(&data[0]); + call_cur=0; + if (info==1) + { + sprintf(tstr,"Block %03d (%05X): 26 - Call Sequence, Number of Calls : %d\n\n",curr+1,block[curr]+10,call_num); + line++; + writeout(tstr); + } + else + { + if (info==2) + { + if (draw) printf(" Call Sequence, Number of Calls : %d\n",call_num); + } + else + { + jump = (signed short)(data[2]+data[3]*256); + if (draw) printf(" Call Sequence, Number of Calls : %d, First: %d (To Block %d)\n",call_num,jump,curr+jump+1); + curr+=jump; + curr--; + } + } +} + +void Analyse_ID27 (void) // Return from Sequence +{ + call_cur++; + if (info==1) + { + sprintf(tstr,"Block %03d (%05X): 27 - Return from Call\n\n",curr+1,block[curr]+10); + line++; + writeout(tstr); + } + else + { + if (info==2) + { + if (draw) printf(" Return from Call\n"); + } + else + { + if (call_cur==call_num) + { + if (draw) printf(" Return from Call, Last Call Finished\n"); + curr=call_pos; + } + else + { + curr = call_pos; + data = &mem[block[curr]+1]; + jump = (signed short)(data[call_cur*2+2]+data[call_cur*2+3]*256); + if (draw) printf(" Return from Call, Calls Left: %d, Next: %d (To Block %d)\n", + call_num-call_cur, jump, curr+jump+1); + curr+=jump; + curr--; + } + } + } +} + +void Analyse_ID28 (void) // Select Block +{ + num_sel=data[2]; + if (info==2) + { + if (draw) + { + sprintf(tstr," Select block"); + MakeFixedString(tstr, 69); + strcpy(tstr+52," (-v for more)"); + printf("%s\n",tstr); + } + } + else + { + if (info==1) + { + sprintf(tstr,"Block %03d (%05X): 28 - Select Block\n",curr+1,block[curr]+10); + writeout(tstr); + data+=3; + for (n=0; n> Press the number!\n"); + PauseWave(5000); // Why?!?!?!?! + amp=LOAMP; + k=getchar(); + if (k==27) Error("ESCAPE key pressed!"); + k-=48; + if (k<1 || k>num_sel) printf("Illegal Selection... Continuing...\n"); + else + { + curr+=jumparray[k-1]; + curr--; + } + } + } +} + +void Analyse_ID2A (void) // Stop the tape if in 48k mode +{ + if (info==1) + { + sprintf(tstr,"Block %03d (%05X): 2A - Stop the tape if in 48k mode\n\n",curr+1,block[curr]+10); + line++; + writeout(tstr); + return; + } + if (info==2) + { + if (draw) printf(" Stop the tape if in 48k mode!\n"); + return; + } + if (mode128) + { + if (draw) printf(" Stop the tape only in 48k mode!\n"); + } + else + { + if (draw) printf(" Stop the tape in 48k mode!\n"); + PauseWave(5000); + amp=LOAMP; + } +} + +void Analyse_ID30 (void) // Description +{ + CopyString(pstr,&data[1],data[0]); + if (info!=1) + { + if (draw) printf(" Description: %s\n",pstr); + } + else + { + sprintf(tstr,"Block %03d (%05X): 30 - Description: %s\n\n",curr+1,block[curr]+10, pstr); + line++; + writeout(tstr); + } +} + +void Analyse_ID31 (void) // Message +{ + CopyString(pstr,&data[2],data[1]); + if (info!=1) + { + // Pause in Message block is ignored ... + if (draw) printf(" Message: %s\n",pstr); + } + else + { + line+=MultiLine(pstr,34,spdstr); + sprintf(tstr,"Block %03d (%05X): 31 - Message: %s\n",curr+1,block[curr]+10, spdstr); + writeout(tstr); + sprintf(tstr," Duration: %d seconds\n\n",data[0]); + line++; + writeout(tstr); + } +} + +void Analyse_ID32 (void) // Archive Info +{ + if (info!=1) + { + if (draw) + { + if (data[3]==0) + { + CopyString(spdstr,&data[5],data[4]); + sprintf(tstr," Title: %s",spdstr); + MakeFixedString(tstr, 69); + strcpy(tstr+52," (-v for more)"); + printf("%s\n",tstr); + } + else + { + sprintf(tstr," Archive Info"); + MakeFixedString(tstr, 69); + strcpy(tstr+52," (-v for more)"); + printf("%s\n",tstr); + } + } + } + else + { + num=data[2]; + data+=3; + sprintf(tstr,"Block %03d (%05X): 32 - Archive Info:\n",curr+1,block[curr]+10); + writeout(tstr); + while(num) + { + switch (data[0]) + { + case 0: sprintf(pstr," Title:"); break; + case 1: sprintf(pstr," Publisher:"); break; + case 2: sprintf(pstr," Author(s):"); break; + case 3: sprintf(pstr," Release Date:"); break; + case 4: sprintf(pstr," Language:"); break; + case 5: sprintf(pstr," Game Type:"); break; + case 6: sprintf(pstr," Price:"); break; + case 7: sprintf(pstr," Loader:"); break; + case 8: sprintf(pstr," Origin:"); break; + default: sprintf(pstr," Comments:"); break; + } + CopyString(spdstr,&data[2],data[1]); + line+=MultiLine(spdstr,16,tstr); + sprintf(spdstr,"%s %s\n",pstr,tstr); + writeout(spdstr); + data+=data[1]+2; + num--; + } + sprintf(tstr,"\n"); + writeout(tstr); + } +} + +void Analyse_ID33 (void) // Hardware Info +{ + if (data[1]==0 && data[2]>0x14 && data[2]<0x1a && data[3]==1) cpc=1; + if (data[1]==0 && data[2]==0x09 && data[3]==1) sam=1; + if (info!=1) + { + if (draw) + { + if (data[1]!=0 || data[3]!=1) + { + sprintf(tstr, " Hardware Type"); + MakeFixedString(tstr, 69); + strcpy(tstr+52," (-v for more)"); + printf("%s\n",tstr); + } + else + { + printf(" This tape is made for %s !\n",hwids[0][data[2]]); + } + } + } + else + { + num=data[0]; + data+=1; + sprintf(tstr,"Block %03d (%05X): 33 - Hardware Info:\n",curr+1,block[curr]+10); + writeout(tstr); + for (n=0; n<4; n++) + { + prvi=1; + d=data; + for (m=0; m0) strcpy(tstr4,"+"); + else strcpy(tstr4," "); + sprintf(pstr,"%s %s %5d %s %s %s\n",spdstr, tstr4, Get2(&data[1]), tstr3, tstr, tstr2); + writeout(pstr); + data+=5; + pstr[0]=0; + } + numt--; + } + } + sprintf(tstr,"\n"); + writeout(tstr); + } +} + +void Analyse_ID40 (void) // Snapshot +{ + if (info!=1) + { + if (draw) printf(" Snapshot (Not Supported yet)\n"); + } + else + { + sprintf(tstr,"Block %03d (%05X): 40 - Snapshot\n\n",curr+1,block[curr]+10); + line++; + writeout(tstr); + switch (data[0]) + { + case 0 : sprintf(pstr,"Type: Z80"); break; + case 1 : sprintf(pstr,"Type: SNA"); break; + default : sprintf(pstr,"Unknown Type"); break; + } + sprintf(tstr," %s\n\n",pstr); + line++; + writeout(tstr); + } +} + +void Analyse_ID5A (void) // ZXTape! +{ + if (info!=1) + { + if (draw) printf(" Start of the new tape (Merged Tapes)\n"); + } + else + { + sprintf(tstr,"Block %03d (%05X): 5A - Merget Tapes\n\n",curr+1,block[curr]+10); + line++; + writeout(tstr); + } +} + +void Analyse_Unknown (void) // Unknown blocks +{ + if (info!=1) + { + if (draw) printf(" Unknown block %02X !\n", id); + } + else + { + sprintf(tstr,"Block %03d (%05X): %02X Unknown Block \n\n",curr+1,block[curr]+10,id); + line++; + writeout(tstr); + } +} + +//////////////////////// +// Main TZX2WAV program +//////////////////////// + +int main(int argc, char *argv[]) +{ + printf("\n=================================================\r\n"); + printf(" TZX to VAV Converter v0.2 Beta (Build %s)\r\n",build); + printf("=================================================\r\n"); + if (argc<2) + { + printf("\nUsage: TZX2WAV [switches] FILE.TZX [FILE.WAV|CSW]\n\n"); + printf("Switches: -f n Set sampling frequency to n Hz (default 44100)\n"); + printf(" -s Generate a 16 bits stereo WAV file\n"); + printf(" -r Reverse the sign of output wave data\n"); + printf(" -c1 Create a CSW v1.01 file instead of a WAV file\n"); + printf(" -c2 Create a CSW v2.00 file instead of a WAV file\n"); + printf(" -i Show information on TZX file structure\n"); + printf(" -v Display a verbose report of TZX file structure\n"); + printf(" -x Expand TZX groups in information output\n"); + printf(" -b n Start conversion at block n\n"); + printf(" -e n End conversion after block n\n"); + printf(" -p Override pause settings in the last block\n"); + printf(" -128 Work in 128K mode\n"); + exit(-1); + } + + // Check for command line options + + for (n=1; nMAJREV) { printf("\n-- Warning: Some blocks may not be recognised and used!\n"); line+=2; } + if (mem[8]==MAJREV && mem[9]>MINREV) { printf("\n-- Warning: Some of the data might not be properly recognised!\n"); line+=2; } + read(ifh,mem,flen-10); + numblocks=0; pos=0; + not_rec=0; + + // Go through the file and record block starts ... + // (not necessary, could just go right through it) + + while(pos < flen-10) + { + block[numblocks]=pos; + pos++; + switch(mem[pos-1]) + { + case 0x10: pos+=Get2(&mem[pos+0x02])+0x04; break; + case 0x11: pos+=Get3(&mem[pos+0x0F])+0x12; break; + case 0x12: pos+=0x04; break; + case 0x13: pos+=(mem[pos+0x00]*0x02)+0x01; break; + case 0x14: pos+=Get3(&mem[pos+0x07])+0x0A; break; + case 0x15: pos+=Get3(&mem[pos+0x05])+0x08; break; + case 0x16: pos+=Get4(&mem[pos+0x00])+0x04; break; + case 0x17: pos+=Get4(&mem[pos+0x00])+0x04; break; + + case 0x20: pos+=0x02; break; + case 0x21: pos+=mem[pos+0x00]+0x01; break; + case 0x22: break; + case 0x23: pos+=0x02; break; + case 0x24: pos+=0x02; break; + case 0x25: break; + case 0x26: pos+=Get2(&mem[pos+0x00])*0x02+0x02; break; + case 0x27: break; + case 0x28: pos+=Get2(&mem[pos+0x00])+0x02; break; + + case 0x2A: pos+=0x04; break; + + case 0x30: pos+=mem[pos+0x00]+0x01; break; + case 0x31: pos+=mem[pos+0x01]+0x02; break; + case 0x32: pos+=Get2(&mem[pos+0x00])+0x02; break; + case 0x33: pos+=(mem[pos+0x00]*0x03)+0x01; break; + case 0x34: pos+=0x08; break; + case 0x35: pos+=Get4(&mem[pos+0x10])+0x14; break; + + case 0x40: pos+=Get3(&mem[pos+0x01])+0x04; break; + + case 0x5A: pos+=0x09; break; + + default : pos+=Get4(&mem[pos+0x00])+0x04; + not_rec=1; + } + numblocks++; + } + + printf("Number of Blocks: %d\n",numblocks); + + if (not_rec) + { + printf("\n-- Warning: Some blocks were *NOT* recognised!\n"); + line+=2; + } + + curr=0; + + if (starting>1) + { + if (starting>numblocks) + { + Error("Invalid Starting Block"); + } + curr=starting-1; + } + + if (ending>0) + { + if (ending>numblocks || ending22) + { + printf("scroll?\n"); + k=getchar(); + if (k==27) Error("ESCAPE key pressed!"); + if (!k) getchar(); + line=0; + } + } + printf("%03d-%05X:",curr+1,block[curr]+10); + } + } + id=mem[block[curr]]; + data=&mem[block[curr]+1]; + switch (id) + { + case 0x10: Analyse_ID10(); // Standard Loading Data block + break; + case 0x11: Analyse_ID11(); // Custom Loading Data block + break; + case 0x12: Analyse_ID12(); // Pure Tone + break; + case 0x13: Analyse_ID13(); // Sequence of Pulses + break; + case 0x14: Analyse_ID14(); // Pure Data + break; + case 0x15: Analyse_ID15(); // Direct Recording + break; + case 0x16: Analyse_ID16(); // C64 ROM Type Data Block + break; + case 0x17: Analyse_ID17(); // C64 Turbo Tape Data Block + break; + case 0x20: Analyse_ID20(); // Pause or Stop the Tape command + break; + case 0x21: Analyse_ID21(); // Group Start + break; + case 0x22: Analyse_ID22(); // Group End + break; + case 0x23: Analyse_ID23(); // Jump To Relative + break; + case 0x24: Analyse_ID24(); // Loop Start + break; + case 0x25: Analyse_ID25(); // Loop End + break; + case 0x26: Analyse_ID26(); // Call Sequence + break; + case 0x27: Analyse_ID27(); // Return from Sequence + break; + case 0x28: Analyse_ID28(); // Select Block + break; + case 0x2A: Analyse_ID2A(); // Stop the tape if in 48k mode + break; + case 0x30: Analyse_ID30(); // Description + break; + case 0x31: Analyse_ID31(); // Message + break; + case 0x32: Analyse_ID32(); // Archive Info + break; + case 0x33: Analyse_ID33(); // Hardware Info + break; + case 0x34: Analyse_ID34(); // Emulation info + break; + case 0x35: Analyse_ID35(); // Custom Info + break; + case 0x40: Analyse_ID40(); // Snapshot + break; + case 0x5A: Analyse_ID5A(); // ZXTape! + break; + default : Analyse_Unknown(); // Unknown blocks + } + + // TZX file blocks analysis finished + // Now we start generating the sound waves + + if (info!=1 && (id==0x10 || id==0x11 || id==0x14)) // One of the data blocks ... + { + if (id!=0x14) Identify(datalen,data,0); + else strcpy(tstr," Pure Data "); + if (id==0x10) sprintf(spdstr,"Normal Speed"); + else sprintf(spdstr," Speed: %3d%%", speed); + sprintf(pstr,"Pause: %5d ms",pause_ms); + if (draw) printf("%s Length:%6d %s %s\n",tstr,datalen,spdstr,pstr); + if (info!=2) + { + while (pilot) // Play PILOT TONE + { + PlayWave(sb_pilot); + ToggleAmp(); + pilot--; + } + if (sb_sync1) // Play first SYNC pulse + { + PlayWave(sb_sync1); + ToggleAmp(); + } + if (sb_sync2) // Play second SYNC pulse + { + PlayWave(sb_sync2); + ToggleAmp(); + } + datapos=0; + while (datalen) // Play actual DATA + { + if (datalen!=1) bitcount=8; + else bitcount=lastbyte; + databyte=data[datapos]; + while (bitcount) + { + if (databyte&0x80) sb_bit=sb_bit1; + else sb_bit=sb_bit0; + PlayWave(sb_bit); // Play first pulse of the bit + ToggleAmp(); + if (!singlepulse) + { + PlayWave(sb_bit); // Play second pulse of the bit + ToggleAmp(); + } + databyte<<=1; + bitcount--; + } + datalen--; datapos++; + } + singlepulse=0; // Reset flag for next TZX blocks + + // If there is pause after block present then make first millisecond the oposite + // pulse of last pulse played and the rest in LOAMP ... otherwise don't do ANY pause + if (pause_ms) + { + PauseWave(1); + amp=LOAMP; + if (pause_ms>1) PauseWave(pause_ms-1); + } + } + } + + if (info!=1 && id==0x16) // C64 ROM data block ... + { + IdentifyC64ROM(datalen, data, 0); + sprintf(pstr,"Pause: %5d ms",pause_ms); + if (draw) printf(" %s Length:%6d %s %s\n",tstr,datalen,spdstr,pstr); + if (info!=2) + { + sb_pilot=Samples(sb_pilot); + sb_sync1=Samples(sb_sync1); sb_sync2=Samples(sb_sync2); + sb_bit1_f=Samples(sb_bit1_f); sb_bit1_s=Samples(sb_bit1_s); + sb_bit0_f=Samples(sb_bit0_f); sb_bit0_s=Samples(sb_bit0_s); + sb_finishbyte_f=Samples(sb_finishbyte_f); + sb_finishbyte_s=Samples(sb_finishbyte_s); + sb_finishdata_f=Samples(sb_finishdata_f); + sb_finishdata_s=Samples(sb_finishdata_s); + sb_trailing=Samples(sb_trailing); + num_lead_in=0; + amp=LOAMP; // This might be just opposite !!!! + while (pilot) // Play PILOT TONE + { + PlayC64(sb_pilot); + pilot--; + } + if (sb_sync1) PlayC64(sb_sync1); // Play SYNC PULSES + if (sb_sync2) PlayC64(sb_sync2); + datapos=0; + while (datalen) // Play actual DATA + { + if (datalen!=1) + { + bitcount=8; + PlayC64ROMByte(data[datapos],0); + } + else + { + bitcount=lastbyte; + PlayC64ROMByte(data[datapos],1); + } + databyte=data[datapos]; + datalen--; datapos++; + } + while (trailing) // Play TRAILING TONE + { + PlayC64(sb_trailing); + trailing--; + } + + // If there is pause after block present then make first millisecond the oposite + // pulse of last pulse played and the rest in LOAMP ... otherwise don't do ANY pause + + if (pause_ms) + { + PauseWave(pause_ms/2); + ToggleAmp(); + PauseWave((pause_ms/2)+(pause_ms%2)); + ToggleAmp(); + } + } + } + + if (info!=1 && id==0x17) // C64 Turbo Tape data block ... + { + IdentifyC64Turbo(datalen, data, 0); + sprintf(pstr,"Pause: %5d ms",pause_ms); + if (draw) printf(" %s Length:%6d %s %s\n",tstr,datalen,spdstr,pstr); + if (info!=2) + { + sb_bit1=Samples(sb_bit1); + sb_bit0=Samples(sb_bit0); + amp=LOAMP; // This might be just opposite !!!! + while (num_lead_in) // Play Lead In bytes + { + bitcount=8; + PlayC64TurboByte(lead_in_byte); + num_lead_in--; + } + datapos=0; + while (datalen) // Play actual DATA + { + if (datalen!=1) bitcount=8; + else bitcount=lastbyte; + PlayC64TurboByte(data[datapos]); + databyte=data[datapos]; + datalen--; datapos++; + } + while (trailing) // Play Trailing bytes + { + bitcount=8; + PlayC64TurboByte((unsigned char)sb_trailing); + trailing--; + } + + // If there is pause after block present then make first millisecond the oposite + // pulse of last pulse played and the rest in LOAMP ... otherwise don't do ANY pause + + if (pause_ms) + { + PauseWave(pause_ms/2); + ToggleAmp(); + PauseWave((pause_ms/2)+(pause_ms%2)); + ToggleAmp(); + } + } + } + + curr++; // We continue to replay the next TZX block + } // This is the main loop end + + if (!info) + { + PauseWave(200); // Finish always with 200 ms of pause after the last block + switch (csw) + { + case 1 : CSW1_Stop(); break; + case 2 : CSW2_Stop(); break; + default : WAV_Stop(); + } + printf("\n%d bytes successfuly written to file.\n",oflen); + } + + GarbageCollector(); + exit(0); + +} // End of TZX2WAV main program diff --git a/tools/tzx2wav/tzx2wav.exe b/tools/tzx2wav/tzx2wav.exe new file mode 100644 index 0000000..a9c90ef Binary files /dev/null and b/tools/tzx2wav/tzx2wav.exe differ diff --git a/tools/tzx2wav/tzx2wav.txt b/tools/tzx2wav/tzx2wav.txt new file mode 100644 index 0000000..fc74b92 --- /dev/null +++ b/tools/tzx2wav/tzx2wav.txt @@ -0,0 +1,146 @@ +///////////////////////////////////////////////////////////////////// +// TZX to VAV Converter v0.2 for Bloodshed Dev-C++ compiler // +// (C) 2005-2006 Francisco Javier Crespo // +// // +// Originally based on source code from these works: // +// PLAYTZX v0.60b for Watcom C compiler (C) 1997-2004 Tomaz Kac // +// PLAYTZX Unix v0.12b (C) 2003 Tero Turtiainen / Fredrick Meunier // +///////////////////////////////////////////////////////////////////// + + TZX2WAV is an utility mainly designed to generate audio WAV files from TZX +data files used in 8-bit computers emulators. A lot of people were demanding a +way to create Audio CD tracks that could be hooked with their real ZX Spectrum +at the highest fidelity. + + TZX2WAV is based on Tomaz Kac's PlayTZX 0.60b, an original tool to replay +TZX through an ISA SoundBlaster card. When PlayTZX was conceived and designed, +the normal operating systems were MS-DOS and Windows 95, and CDROM burners +were a expensive luxury. Now Windows 2000 and XP are the common environments, +and I felt the best way to achieve our aim, was a tool to create standard WAV +files that could be easily imported into any CD burning program. + +These are the only requirements for the supplied executable of TZX2WAV: +- Win32 compatible environment (W95, W98, WMe, NT, W2K, WXP and maybe Wine) +- Enough storage (Hard Drive, Memory Sticks or whatever) for the output file + + This version of TZX2WAV is able to handle TZX Revision 1.13 files. The next +release will be compatible with TZX Revision 1.20 Draft, which will support +some encoding methods not available in the current specification. + + Mono WAV files generated by this version should work with all emulators. +I have tested them with R80, EmuZWin, SPIN, RealSpectrum and Spectaculator. + + TZX2WAV 0.2b is able to create both CSW 1.01 (RLE compression) and CSW 2.00 +(ZLIB compression) files so they should work in all CSW-aware emulators. + + All data blocks (10-14) should work fine. "Direct Recording" block should +work OK if you use the sampling rate that is the same, or a multiply of the +original sampling rate that was used to create the "Direct Recording" block +(i.e. if was created using 11025 Hz, you may use 22050 or 44100 Hz). Also the +"Pause" and "Stop the tape" commands work just fine and can be used. + + TZX2WAV can display information on internal TZX file structure, instead of +creating any output file, using "-i" switch. A verbosely extended information +is available using "-v" switch, for those interested in the inners of TZX. + + By default, the groups are not displayed expanded, i.e. only the group name +is shown and not what is inside that group. If you would like to expand the +groups, then use the "-x" switch. This is only relevant when displaying info +one line per block (or converting the TZX tape). In verbose mode, ALL blocks +are always displayed. + + Some Amstrad CPC, SAM Coupe and Commodore 64 files are identified with +specific "Hardware Info" block. Some internal header names in these platforms +are able to be displayed if recognized. Non-standard loaders will just be +represented by the length of the block and names won't be displayed on screen, +but correct WAV/CSW files will be created. + + If no output file is given, then the program will use the same filename as +the .TZX one, and change the extension to .WAV or .CSW accordingly. + + If you don't want to convert or see all blocks then you can specify the +starting and ending block with "-b" and "-e" switches. + +----------------------------------------------------------------------------- + +Command Line Syntax: TZX2WAV [switches] FILE.TZX [FILE.WAV|CSW] + +-f n : This will set the sampling frequency to n Hz. The default sampling + frequency for WAV conversion is 44100 Hz, so it doesn't need to be + resampled for CD Audio, but if you wish higher fidelity or even + storage for DVD, you can generate 48000 Hz or any other rate. + +-s : This NEW switch will automatically generate STEREO WAV files that + can be directly imported into any burning CD program without any + further processing, or channel duplication. + +-r : This will reverse the signal of the whole wave data. If a C64 tape + does not work on the C64, then try using this switch ! + +-c1 : This will create a .CSW file instead generating a .WAV mono file. + CSW v1.01 was the first specification of these format. The highest + frequency that can be used here is 65535 Hz (16-bit counter). + +-c2 : This will create a .CSW file instead generating a .WAV mono file. + CSW v2.00 offers far more compression than CSW v1.01 format. + When converting extremely high-speed loading files, it's suggested to + use 96000 Hz sampling frequency with CSW 2.00, as size won't increase. + +-i : This will show ONE line of information per block, just displaying the + same output when in normal operation, but the WAV/CWS files will NOT + be created. The starting byte (in hexadecimal format) of each block + is written after the block number. + +-v : This will show very extensive information about all the blocks within + the TZX file. Some blocks have not yet been tested (such as Snapshot, + or Custom Info) but they *should* work OK anyway. The starting byte + of each block is also written in the () brackets. + +-x : Will expand the tape groups. By default the groups are displayed in + collapsed mode (i.e. only the group name is displayed and not the + blocks in it) but with this switch ALL blocks will be displayed. + +-b n : Start conversion of the tape at block n. The first block got the + number 1. ALL blocks (including Info, group,...) are numbered. + +-e n : Stop conversion of the tape AFTER block n. Block n will be played. + (i.e. if you use -b 3 -e 5 then blocks 3, 4 and 5 will be played) + +-p : This NEW switch changes its meaning. Now when used, the pause value of + the last block in TZX file is overridden and set to zero, in order to + deal with some malformed TZX files. Anyway, TZX2WAV will *always* add + 200 milliseconds of silence at the end of the wave file. + +-128 : This will activate 128K mode. Only relevant if the TZX file has any + "Stop the tape if in 48K mode" special block. If enabled, this + switch will ignore all these blocks. Otherwise 5 seconds of silence. + will be generated in the wave file. + +----------------------------------------------------------------------------- + +* TZX2WAV 0.2 Beta (25th February 2006) + - Stereo WAV files can be created for direct import into CD burning tools. + - CSW 2.0 ZLIB comppresed files can be generated with "-c2" switch. + - We can override the pause information of the last block with "p" switch. + - The program C source has been optimized and severely reorganized. + +* TZX2WAV 0.1 Beta (15th January 2005) + - First Public release based on PLAYTZX 0.60b + + Source code is provided with the official archive release, just in case you +would like to port this utility to some other computer or operating system. +The zlib library source MUST be downloaded separately from http://www.zlib.net + + Do not hesitate to contact me at the address on top of this document for +bug report or other kind of feedback. + + The latest revision of TZX2WAV, the TZX format docs and other TZX utilities +amongst other TZX game files, should be available at "World of Spectrum" web, +which is mantained by Martijn van der Heide: + + http://www.worldofspectrum.org/ + +----------------------------------------------------------------------------- + +Francisco Javier "Black Hole" Crespo +Madrid @ Spain - 2006