///////////////////////////////////////////////////////////////////// // TZX to VAV Converter v0.2 for Bloodshed Dev-C++ compiler // // (C) 2005-2006 Francisco Javier Crespo // // // // MiSTer adaptation (CSW v1 only) // // (C) 2017 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 // ///////////////////////////////////////////////////////////////////// #include #include #include #include #include #include #include #include "tzx2wav.h" #include "spi.h" // Computer entries const char *hwids_01[] = { "ZX Spectrum 16k", "ZX Spectrum 48k, Plus", "ZX Spectrum 48k Issue 1", "ZX Spectrum 128k (Sinclair)", "ZX Spectrum 128k +2 (Grey case)", "ZX Spectrum 128k +2A, +3", "Timex Sinclair TC-2048", "Timex Sinclair TS-2068", "Pentagon 128", "Sam Coupe", "Didaktik M", "Didaktik Gama", "ZX-81 with 1k RAM", "ZX-81 with 16k RAM or more", "ZX Spectrum 128k, Spanish version", "ZX Spectrum, Arabic version", "TK 90-X", "TK 95", "Byte", "Elwro", "ZS Scorpion", "Amstrad CPC 464", "Amstrad CPC 664", "Amstrad CPC 6128", "Amstrad CPC 464+", "Amstrad CPC 6128+", "Jupiter ACE" "Enterprise", "Commodore 64", "Commodore 128" }; #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) static unsigned int freq = 44100; // Default Sample Frequency static unsigned char *mem = 0; // File in Memory static int pos; // Position in File static int curr; // Current block that is playing static int numblocks; // Total Num. of blocks static unsigned long oflen; // Length of output file static int block[2048]; // Array of Block starts static double cycle; // Frequency / 3500000 (Z80 clock) static int cpc=0; // Amstrad CPC tape ? static int sam=0; // SAM Coupe tape ? static int id; // Current Block ID static int pilot; // Len of Pilot signal (in hp's) static int sb_pilot; // Pilot pulse static int sb_sync1; // Sync first half-period (hp) static int sb_sync2; // Sync second static int sb_bit0; // Bit-0 static int sb_bit1; // Bit-1 static int sb_pulse; // Pulse in Sequence of pulses and direct recording block static int lastbyte; // How many bits are in last byte of data ? static int pause_ms; // Pause after current block (in milliseconds) static int skippause=0; // Overrides pause value in last TZX block static int singlepulse; // Flag to activate single pulse waves static int manchester; // Flag to activate manchester encoded waves static unsigned char *data; // Data to be played static int datalen; // Len of ^^^ static int datapos; // Position in ^^^ static int bitcount; // How many bits to play in current byte ? static int sb_bit; // should we play bit 0 or 1 ? static char databyte; // Current Byte to be replayed of the data static signed short jump; // Relative Jump static int not_rec; // Some blocks were not recognised ?? static int starting=1; // starting block static int ending=0; // ending block static int expand=0; // Expand Groups ? static int draw=1; // Local flag for outputing a line when in a group static int speed; static int loop_start=0; // Position of the last Loop Start block static int loop_count=0; // Counter of the Loop static int call_pos=0; // Position of the last Call Sequence block static int call_num=0; // Number of Calls in the last Call Sequence block static int call_cur=0; // Current Call to be made static int num_sel; // Number of Selections in the Select block static int jumparray[256]; // Array of all possible jumps in Select block static 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; static int trailing, sb_trailing; static char lead_in_byte; static int endian; static char add_bit; static int inv = 0; static char tstr[255]; static char spdstr[255]; static char pstr[255]; static void core_write(const void *buf, int size) { const char *addr = (const char*)buf; while (size--) { spi8(*addr++); oflen++; } } /////////////////////////////// // CSW v1.01 handling routines /////////////////////////////// static int amp, cswamp; static unsigned int cswlen; 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; core_write("Compressed Square Wave\032", 23); core_write(&Revision, 2); // Major & Minor revision core_write(&freq, 2); // Sample Rate core_write(&CompType, 1); // Compression Type core_write(&inv, 1); // Polarity core_write(&Reserved, 3); // Reserved bytes cswamp = LOAMP; cswlen = 0; } void CSW1_Write(unsigned int samples) { if (cswamp == amp) { cswlen += samples; return; } if (cswlen < 256) { core_write(&cswlen, 1); } else { int zero = 0; core_write(&zero, 1); core_write(&cswlen, 4); } cswamp = amp; cswlen = samples; } ////////////////////////////////// // 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) { CSW1_Write(len); } void PauseWave(unsigned int pause_ms) { // Waits for "pause" milliseconds int p; if (((!skippause) || (curr != (numblocks - 1)))) { if (!curr && pause_ms > 2000) pause_ms = 2000; p = (unsigned int)((((float)pause_ms)*freq) / 1000.0); PlayWave(p); } } void PlayFinish() { if (cswlen) { ToggleAmp(); CSW1_Write(0); } } ///////////////////////////// // 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; int n = 0; for (; 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; int n = 0; for (; 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 ///////////////////////// void CopyString(char *destination, unsigned char *source, unsigned int len) { // Could just use strcpy ... unsigned int n; for (n = 0; n < len; n++) destination[n] = source[n]; destination[n] = 0; } void MakeFixedString(char *s, int i) { // This will create a fixed length string from null-terminated one... int n = 0; int k = 0; while (i) { if (!s[n]) k = 1; if (k) s[n] = ' '; n++; i--; } s[n] = 0; } /////////////////////////////// // TZX Blocks Parsing routines /////////////////////////////// void Analyse_ID10(void) // Standard Loading Data block { pause_ms = Get2(&data[0]); datalen = Get2(&data[2]); data += 4; if (data[0] == 0x00) pilot = 8064; else pilot = 3220; sb_pilot = Samples(2168); sb_sync1 = Samples(667); sb_sync2 = Samples(735); sb_bit0 = Samples(885); sb_bit1 = Samples(1710); lastbyte = 8; } void Analyse_ID11(void) // Custom Loading Data block { sb_pilot = Samples(Get2(&data[0])); sb_sync1 = Samples(Get2(&data[2])); sb_sync2 = Samples(Get2(&data[4])); sb_bit0 = Samples(Get2(&data[6])); sb_bit1 = Samples(Get2(&data[8])); speed = (int)((1710.0 / (double)Get2(&data[8]))*100.0); pilot = Get2(&data[10]); lastbyte = (int)data[12]; pause_ms = Get2(&data[13]); datalen = Get3(&data[15]); data += 18; } void Analyse_ID12(void) // Pure Tone { sb_pilot = Samples(Get2(&data[0])); pilot = Get2(&data[2]); if (draw) printf(" Pure Tone Length: %5d\n", pilot); while (pilot) { PlayWave(sb_pilot); ToggleAmp(); pilot--; } } void Analyse_ID13(void) // Sequence of Pulses { pilot = (int)data[0]; data++; if (draw) printf(" Sequence of Pulses Length: %5d\n", pilot); while (pilot) { sb_pulse = Samples(Get2(&data[0])); PlayWave(sb_pulse); ToggleAmp(); pilot--; data += 2; } } void Analyse_ID14(void) // Pure Data { sb_pilot = pilot = sb_sync1 = sb_sync2 = 0; sb_bit0 = Samples(Get2(&data[0])); sb_bit1 = Samples(Get2(&data[2])); speed = (int)((1710.0 / (double)Get2(&data[2]))*100.0); lastbyte = (int)data[4]; pause_ms = Get2(&data[5]); datalen = Get3(&data[7]); data += 10; } void Analyse_ID15(void) // Direct Recording { // For now the BEST way is to use the sample frequency for replay that is // exactly the SAME as the Original Freq. used when sampling this block ! // i.e. NO downsampling is handled YET ... use TAPER when you need it ! ;-) sb_pulse = Samples(Get2(&data[0])); if (!sb_pulse) sb_pulse = 1; // In case sample frequency > 44100 pause_ms = Get2(&data[2]); // (Should work for frequencies upto 48000) lastbyte = (int)data[4]; datalen = Get3(&data[5]); if (draw) printf(" Direct Recording Length:%6d Original Freq.: %5d Hz\n", datalen, (int)(0.5 + (3500000.0 / (double)Get2(&data[0])))); 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(); if (pause_ms) PauseWave(pause_ms); } 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); } 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); } void Analyse_ID20(void) // Pause or Stop the Tape command { pause_ms = Get2(&data[0]); amp = LOAMP; if (pause_ms) { if (draw) printf(" Pause Length: %2.3fs\n", ((float)pause_ms) / 1000.0); PauseWave(pause_ms); amp = LOAMP; } else { if (draw) printf(" Stop the tape command!\n"); PauseWave(2000); // 2 seconds of pause in "Stop Tape" wave output amp = LOAMP; } } void Analyse_ID21(void) // Group Start { CopyString(pstr, &data[1], data[0]); if (draw) printf(" Group: %s\n", pstr); if (!expand) draw = 0; } void Analyse_ID22(void) // Group End { if (draw) printf(" Group End\n"); draw = 1; } void Analyse_ID23(void) // Jump To Relative { jump = (signed short)(data[0] + data[1] * 256); if (draw) printf(" Jump Relative: %d (To Block %d)\n", jump, curr + jump + 1); curr += jump; curr--; } void Analyse_ID24(void) // Loop Start { loop_start = curr; loop_count = Get2(&data[0]); if (draw) printf(" Loop Start, Counter: %d\n", loop_count); } void Analyse_ID25(void) // Loop End { 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"); } } void Analyse_ID26(void) // Call Sequence { call_pos = curr; call_num = Get2(&data[0]); call_cur = 0; 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 (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]; printf(" Select :\n"); data += 3; for (int n = 0; n < num_sel; n++) { jump = (signed short)(data[0] + data[1] * 256); jumparray[n] = jump; CopyString(spdstr, &data[3], data[2]); printf("%5d : %s\n", n + 1, spdstr); data += 3 + data[2]; } //no interactive shell. choose 1. PauseWave(200); int k = 1; amp = LOAMP; /* printf(">> Press the number!\n"); PauseWave(5000); // Why?!?!?!?! 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 (draw) printf(" Stop the tape in 48k mode!\n"); PauseWave(3000); amp=LOAMP; } void Analyse_ID30(void) // Description { CopyString(pstr, &data[1], data[0]); if (draw) printf(" Description: %s\n", pstr); } void Analyse_ID31(void) // Message { CopyString(pstr, &data[2], data[1]); // Pause in Message block is ignored ... if (draw) printf(" Message: %s\n", pstr); } void Analyse_ID32(void) // Archive Info { 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); } } } 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 (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_01[data[2]]); } } } void Analyse_ID34(void) // Emulation info { if (draw) printf(" Information for emulators.\n"); } void Analyse_ID35(void) // Custom Info { CopyString(pstr, data, 16); if (draw) { if (strcmp(pstr, "POKEs ")) printf(" Custom Info: %s\n", pstr); // Only Name of Custom info except POKEs is used ... else { sprintf(tstr, " Custom Info: %s", pstr); MakeFixedString(tstr, 69); strcpy(tstr + 52, " (-v for more)"); printf("%s\n", tstr); } } } void Analyse_ID40(void) // Snapshot { if (draw) printf(" Snapshot (Not Supported yet)\n"); } void Analyse_ID5A(void) // ZXTape! { if (draw) printf(" Start of the new tape (Merged Tapes)\n"); } void Analyse_Unknown(void) // Unknown blocks { if (draw) printf(" Unknown block %02X !\n", id); } //////////////////////// // Main TZX2WAV program //////////////////////// int tzx2csw(fileTYPE *f) { freq = 44100; starting = 1; ending = 0; expand = 0; skippause = 0; inv = 0; oflen = 0; mem = (unsigned char *)malloc(f->size); if (mem == NULL) { printf("\n-- Not enough memory to load the file!"); return 0; } // Start reading file... FileReadAdv(f, mem, 10); mem[7] = 0; if (strcmp((const char*)mem, "ZXTape!")) { printf("\n-- File is not in ZXTape format!"); free(mem); return 0; } printf("\nZXTape file revision %d.%02d\n", mem[8], mem[9]); if (!mem[8]) { printf("\n-- Development versions of ZXTape format are not supported!"); free(mem); return 0; } if (mem[8] > MAJREV) printf("\n-- Warning: Some blocks may not be recognised and used!\n"); if (mem[8] == MAJREV && mem[9] > MINREV) printf("\n-- Warning: Some of the data might not be properly recognised!\n"); FileReadAdv(f, mem, f->size - 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 < f->size - 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"); } curr = 0; if (starting > 1) { if (starting > numblocks) { printf("\n-- Invalid Starting Block"); free(mem); return 0; } curr = starting - 1; } if (ending > 0) { if (ending > numblocks || ending < starting) { printf("\n-- Invalid Ending Block"); free(mem); return 0; } numblocks = ending; } printf("\nCreating CSW v1"); printf(" file using %d Hz frequency ...\n\n", freq); CSW1_Init(); amp = LOAMP; singlepulse = 0; manchester = 0; cycle = (double)freq / 3500000.0; // This is for the conversion later ... ///////////////////////////////////////////////////// // Start replay of blocks (Main loop of the program) ///////////////////////////////////////////////////// while (curr < numblocks) { if (draw) printf("Block %03d:", curr + 1); 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 ((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); { 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 (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); { 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 (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); { 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 PauseWave(200); // Finish always with 200 ms of pause after the last block PlayFinish(); printf("\n%lu bytes sent to the core.\n", oflen); free(mem); return 1; }