I will try to explain where the problem is, in my broken English :
Synchronizing the byte reading is crucial, because the data stream is written and read synchronously on the compact cassette. That means the bits must be written and read at the same time, neither more nor less, like a real MO does.
The gap between blocks is as important as data. The program could do something that takes more or less time between blocks, and the developer could have found not very useful to stop the motor during that time. So gap lengths could vary. Unfortunately, the K7 file format does not save that kind of information.
The reading is not at its best without to be approximate: the range of time between the change of the signal and the check itself is roughly equivalent to a hundred of cycles, even though the amount of cycles to read a full bit of data is about 800 (here for a bit set to 1):
Fichier(s) joint(s):
graph1.png [ 4.37 Kio | Vu 31627 fois ]
Moreover, the speed between two tape readers could be different, due to the variability of adjustment and/or the oldness of the equipment. To "correct" all that, I have added the option '--modified' in the command line, that divides the synchro/change/check in three equal parts, and get sometimes a better result even with worn magnetic tapes. It could correct speed differences as well (here for a bit set to 1):
Fichier(s) joint(s):
graph2.png [ 4.38 Kio | Vu 31627 fois ]
The k5towav program:
Usage: k5towav [--modified] <filename>
--modified : modify the shape of signal to try to correct read errors (optional)
<filename> : a K5 file name. The WAV output will be saved in the same directory under the same name with the suffix ".wav" (16 bits/44100Hz)
The makefile :
Code:
CC=gcc
CFLAGS= -W -Wall -Werror -O2
all : k5towav
The k5towav C source :
Code:
/*--------------------------------------------------------*/
/* k5towav (c) Prehisto feb 2015 */
/* To convert MO K7 file to WAV file */
/*--------------------------------------------------------*/
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#ifndef TRUE
# define TRUE 1
#endif
#ifndef FALSE
# define FALSE 0
#endif
/* WAV constants */
#define WAVE_HEADER_SIZE 0x2c
#define WAVE_RIFF_CHUNK_SIZE 4
#define WAVE_FORMAT_CHUNK_SIZE 16
#define WAVE_FORMAT_PCM 1
#define WAVE_FORMAT_CHANNELS 1
#define WAVE_FORMAT_SAMPLESPERSEC 44100
#define WAVE_FORMAT_BLOCKALIGN 2
#define WAVE_FORMAT_AVGBYTEPERSEC WAVE_FORMAT_SAMPLESPERSEC*WAVE_FORMAT_BLOCKALIGN
#define WAVE_FORMAT_BITPERSAMPLE 16
/* Sample parameters */
#define CYCLES_PER_FRAME 19968
#define MICROSEC_PER_FRAME 20000
#define MICROSEC_PER_SEC 1000000
#define CURRENT_VALUE_DEFAULT 0x7fff
static int flag_modified = FALSE;
static int current_value = CURRENT_VALUE_DEFAULT;
static double remain = 0;
static int cycles = 0;
static void write_gap (int length, FILE *file)
{
int i;
for (i=0; i<length; i++)
{
fputc (current_value&0xff, file );
fputc ((current_value>>8)&0xff, file );
}
}
static void write_motoroff (FILE *file)
{
write_gap (WAVE_FORMAT_SAMPLESPERSEC/2, file);
}
static void write_motoron (FILE *file)
{
write_gap (WAVE_FORMAT_SAMPLESPERSEC, file);
}
static void write_data_gap (FILE *file)
{
/* Must have time to do at least :
4+2 NEXT LDA ,X+
4+2 STA ,U+
4+1 LEAY -1,U
3 BNE NEXT
... a bit more! */
write_gap (WAVE_FORMAT_SAMPLESPERSEC/8, file);
}
static void write_signal (FILE *file)
{
int i;
double size = ((((double)cycles*(double)MICROSEC_PER_FRAME)
/(double)CYCLES_PER_FRAME)
*(double)WAVE_FORMAT_SAMPLESPERSEC)
/(double)MICROSEC_PER_SEC;
size += remain-(double)((int)remain);
remain = size;
/* write signal */
for (i=0; i<(int)size; i++)
{
fputc (current_value&0xff, file );
fputc ((current_value>>8)&0xff, file );
}
/* invert signal */
current_value=~current_value;
cycles = 0;
}
static void write_byte_standard (char byte, FILE *file)
{
int bit;
/* 4 LF1AF STA <$2045
2 LDB #$08 */
cycles += 4+2;
for (bit=0x80; bit>0; bit>>=1)
{
/* 7 LF1B3 BSR LF1CB
2 LF1CB LDA #$40
4+0 EORA ,U
4+0 STA ,U */
cycles += 7+2+4+4;
write_signal (file);
/* 5 RTS
3 LDX #$002D
7 BSR LF1A2
4+1 LF1A2 LEAX -$01,X
3 BNE LF1A2
2 CLRA
5 RTS
3 LDX #$0032
6 ASL <$2045
3 BCC LF1C5 */
cycles += 5+3+7+(0x2d*(4+1+3))+2+5+3+6+3;
if (((int)byte & bit) == 0)
{
/* 7 LF1C5 BSR LF1A2
4+1 LF1A2 LEAX -$01,X
3 BNE LF1A2
2 CLRA
5 RTS */
cycles += 7+(0x32*(4+1+3))+2+5;
}
else
{
/* 7 BSR LF1CB
2 LF1CB LDA #$40
4+0 EORA ,U
4+0 STA ,U */
cycles += 7+2+4+4;
write_signal (file);
/* 5 RTS
4+1 LEAX -$03,X
7 LF1C5 BSR LF1A2
4+1 LF1A2 LEAX -$01,X
3 BNE LF1A2
2 CLRA
5 RTS */
cycles += 5+4+1+7+((0x32-3)*(4+1+3))+2+5;
}
/* 2 DECB
3 BNE LF1B3 */
cycles += 5;
}
/* 5 RTS */
cycles += 5;
}
static void write_byte_modified (char byte, FILE *file)
{
int bit;
/* 4 LF1AF STA <$2045
2 LDB #$08 */
cycles += 4+2;
for (bit=0x80; bit>0; bit>>=1)
{
/* 7 LF1B3 BSR LF1CB
2 LF1CB LDA #$40
4+0 EORA ,U
4+0 STA ,U */
cycles += 7+2+4+4;
write_signal (file);
cycles += 269;
if (((int)byte & bit) == 1)
{
write_signal (file);
}
cycles += 564;
/* 2 DECB
3 BNE LF1B3 */
cycles += 5;
}
/* 5 RTS */
cycles += 5;
}
static void write_byte (char byte, FILE *file)
{
if (flag_modified == FALSE)
write_byte_standard (byte, file);
else
write_byte_modified (byte, file);
}
#define STOCK_LENGTH 40
#define MOTOR_STOPS_AFTER_THIS_BLOCK 0x01
#define MOTOR_STOPS_AFTER_DATA_BLOCK 0x02
static void convert_file (FILE *bin_file, FILE *wav_file)
{
static char stock[STOCK_LENGTH];
static size_t length = 0;
static size_t size = 0;
static int motor_status = 0;
char dcmoto_mark1[] = "DCMOTO\1\1\1\1\1\1\1\1\1\1\x3c\x5a";
char dcmoto_mark2[] = "\xdc\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\x3c\x5a";
char basic_mark[] = "\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\1\x3c\x5a";
do
{
if ((length < STOCK_LENGTH) && (feof (bin_file) == 0))
{
size = fread(stock+length, 1, STOCK_LENGTH-length, bin_file);
length += size;
}
if (length > 19)
{
if ((memcmp (dcmoto_mark1, stock, 18) == 0)
|| (memcmp (dcmoto_mark2, stock, 18) == 0)
|| (memcmp (basic_mark, stock, 18) == 0))
{
if (motor_status != 0)
{
write_motoroff (wav_file);
motor_status &= ~MOTOR_STOPS_AFTER_THIS_BLOCK;
}
memset (stock, 0x01, 6);
cycles = 0;
remain = 0;
if (stock[18] == '\0')
{
write_motoron (wav_file);
motor_status &= ~MOTOR_STOPS_AFTER_DATA_BLOCK;
if ((length > (18+2+0x0d)) && (stock[18+2+0x0d] != '\0'))
{
motor_status |= MOTOR_STOPS_AFTER_DATA_BLOCK;
}
motor_status |= MOTOR_STOPS_AFTER_THIS_BLOCK;
}
else
{
if ((motor_status & MOTOR_STOPS_AFTER_DATA_BLOCK) != 0)
{
write_motoron (wav_file);
}
else
{
write_data_gap (wav_file);
}
if (stock[18] == '\xff')
{
motor_status &= ~MOTOR_STOPS_AFTER_DATA_BLOCK;
motor_status |= MOTOR_STOPS_AFTER_THIS_BLOCK;
}
else
{
motor_status &= ~MOTOR_STOPS_AFTER_THIS_BLOCK;
}
}
}
}
cycles += 4+2+7; /* For preparing data to be written */
write_byte (*stock, wav_file);
cycles += 6+3; /* For the loop */
memmove (stock, stock+1, STOCK_LENGTH-1);
length--;
} while (length > 0);
}
static void write_le4 (int value, FILE *file)
{
fputc( value&0xff, file );
fputc( (value>>8)&0xff, file );
fputc( (value>>16)&0xff, file );
fputc( (value>>24)&0xff, file );
}
static void write_le2 (int value, FILE *file)
{
fputc( value&0xff, file );
fputc( (value>>8)&0xff, file );
}
static void write_id (char *id, FILE *file)
{
int size = fwrite (id, 1, 4, file);
(void)size;
}
static void update_wav_header (char *filename)
{
FILE *file;
struct stat st;
stat( filename, &st );
file = fopen (filename, "rb+");
if (file != NULL)
{
write_id ("RIFF", file);
write_le4 ((int)st.st_size-WAVE_RIFF_CHUNK_SIZE, file);
write_id ("WAVE", file);
write_id ("fmt ", file);
write_le4 (WAVE_FORMAT_CHUNK_SIZE, file);
write_le2 (WAVE_FORMAT_PCM, file);
write_le2 (WAVE_FORMAT_CHANNELS, file);
write_le4 (WAVE_FORMAT_SAMPLESPERSEC, file);
write_le4 (WAVE_FORMAT_AVGBYTEPERSEC, file);
write_le2 (WAVE_FORMAT_BLOCKALIGN, file);
write_le2 (WAVE_FORMAT_BITPERSAMPLE, file);
write_id ("data", file);
write_le4 ((int)st.st_size-WAVE_HEADER_SIZE, file);
fclose (file);
}
}
static void write_wav (char *bin_name, char *wav_name)
{
int i;
FILE *bin_file;
FILE *wav_file;
bin_file = fopen (bin_name, "rb");
if (bin_file != NULL)
{
wav_file = fopen (wav_name, "wb");
if (wav_file != NULL)
{
/* Create WAV header gap */
for (i=0; i<WAVE_HEADER_SIZE; i++)
{
fputc (0x00, wav_file);
}
/* Write WAV data */
write_motoron (wav_file);
convert_file (bin_file, wav_file);
write_motoroff (wav_file);
fclose (wav_file);
update_wav_header (wav_name);
}
else
{
printf ("*** Can not write file %s\n", wav_name);
}
fclose (bin_file);
}
else
{
printf ("*** Can not read file %s\n", bin_name);
}
}
static int info (void)
{
printf ("k5towav (c) Prehisto feb 2015\n");
printf (" Usage : k5towav [--modified] <filename>\n");
return EXIT_FAILURE;
}
int main (int argc, char *argv[])
{
int i;
char bin_name[300] = "";
char wav_name[300] = "";
for (i=1; i<argc; i++)
{
if (argv[i][0] == '-')
{
if (strcmp (argv[i], "--modified") == 0)
{
flag_modified = TRUE;
}
else
{
printf ("*** option '%s' unknown\n", argv[i]);
return info ();
}
}
else
{
if (*bin_name == '\0')
{
strcpy(bin_name, argv[i]);
}
else
{
return info ();
}
}
}
sprintf (wav_name, "%s.wav", bin_name);
write_wav (bin_name, wav_name);
return EXIT_SUCCESS;
}
Conclusion: The K7 file format is just bad. Good to convert from a WAV, but totally inappropriate to convert into a WAV, especially if the file is corrupted by emulator adaptation to get round protections, like games. It is time to save the hard content of Thomson compact cassettes before it's too late...
EDIT 20 feb 2015 15:56 : The program recognizes now few DCMOTO marks. But, except to correct bugs, I will not go further. This program is just an example.
EDIT 20 feb 2015 19:47 : Bug correction. MICROSEC_PER_FRAME was increased by 10 in the calculation of 'size' in write_signal() , what was useless. Just a trace of test.
EDIT 21 feb 2015 21:51 : Simplify the code.
EDIT 21 feb 2015 22:53 : Correction of offset, condition and compilation error.
EDIT 22 feb 2015 18:27 : Simplify the code.