Sinclair Spectrum casette reading using PC
Casette format
The following casette format information can be found from book "An Expert Guide to the Spectrum" written by Mike James and published by Granada. I have found the information be quite accurate.
Spectrum casette file consist of two parts: header part and the actual file part. Both parts have same basic structure: first there is loader tone, then a sync pulse and after it the actual data.
Loader tone signal signal is 614.9 microsecond low and 614.9 microseconds high. The sunc pulse is 190.6 microseconds low and 210 microseconds high. In data part one bit is 488,6 microseconds low and 488.6 microsecons high. Zero bit is 244.3 microseconds low and 244.3 microsecond.
The data is stored so that most significant bit of byte is saved first and least signicant bit last. The first byte of the data part of file tells the type of file and the actual data follows after it. The last byte in data part is cheksum which is obtained by xoring all data bytes together.
Decoding program
And here is a simple Turbo Pascal program which I have used to load files from spectrum tapes. It works nicely when Spectrum is directly connected to SoundBlaster and I save file in spectrum and start that loading program in PC. With casette deck test have been less succesful. The program loads only the header part, but can be easily extended.
Program Spectrum_to_PC; { Sinclair Spectrum casette loader for IBM PC compatibles with Sound Blaster. Sound Blaster must be at default I/O address 220h. Copyright 1992 Tomi Engdahl Sinclair Spectrum is a registered trade mark of Sinclair Reserch Ltd. Sound Blaster is a register trade mark of Creative Labs Inc. } Uses crt; Var timer_count:word; tmp:byte; old_bit:byte; Const Timer0=$40; TimerCtlr=$43; TimerClk=1193180; Audio0=128; hysteresis=20; mid_value=2000; {2000} ltone_HI=4000; Procedure CLI; Begin Inline($FA); {cli, disable interrupts} End; Procedure STI; Begin Inline($FB); {cli, enable interrupts} End; Function Timer0Read:word; Var value:word; Begin Port[TimerCtlr]:=0; {latch timer 0} value:=Port[Timer0]; value:=value+(Port[Timer0] shl 8); Timer0Read:=value; End; Function time_difference:word; Var value:word; Begin {R-} Port[TimerCtlr]:=0; {latch timer 0} value:=Port[Timer0]; value:=value+(Port[Timer0] shl 8); time_difference:=timer_count-value; timer_count:=value; {R+} End; Function Read_SBADC:byte; Begin Inline( $BA/$2C/$02/ {MOV DX,022C } $EC/ {IN AL,DX } $A8/$80/ {TEST AL,80 } $75/$FB/ {JNZ 0107 } $B0/$20/ {MOV AL,20 } $EE/ {OUT DX,AL } $BA/$2E/$02/ {MOV DX,022E } $EC/ {IN AL,DX } $A8/$80/ {TEST AL,80 } $74/$FB/ {JZ 0112 } $BA/$2A/$02/ {MOV DX,022A } $EC/ {IN AL,DX } $A2/tmp); {MOV a,AL } Read_SBADC:=tmp; End; {Function schmitt_trigger(value:byte):byte; Begin If old_bit=0 Then If value>(audio0+hysteresis) Then old_bit:=1; If old_bit=1 Then If value<(audio0-hysteresis) Then old_bit:=0; schmitt_trigger:=old_bit; End;} Function schmitt_trigger(value:byte):byte; Begin If old_bit=0 Then If value>(audio0+hysteresis) Then old_bit:=1; If old_bit=1 Then If value<(audio0-hysteresis) Then old_bit:=0; schmitt_trigger:=old_bit; End; Function bit_from_tape:byte; Var adcvalue:byte; count:integer; Begin Repeat Until schmitt_trigger(Read_SBADC)=0; Rei_difference>mid_value Then bit_m_tape:=1 Else bit_from_tape:=0; End; Procedure Wait_loader_tone; Var diff:word; count:integer; Begin diff:=0; Repeat Repeat Until schmitt_trigger(Read_SBADC)=0; Repeat Until schmitt_trigger(Read_SBADC)=1; diff:=time_difference; If (diff>mid_value) and (diff<Ltone_hi) Then Inc(count) Else count:=0; Until count>=10; End; {******* High level packet routines ************* } Var tavut:array[0..50000] of byte; Procedure load_bytes(maara:word); Var tavu,p,a,n:byte; laskuri:word; Begin ClrScr; Repeat Until bit_from_tape=1; {wait for loader tone} Writeln('loader tone detected'); Repeat Until bit_from_tape=0; {synclse wait} {Writeln('sync');} For n:=0 to 7 Do p:=bit_from_tape; {over the start byte} laskuri:=0; Repeat tavu:=0; For n:=0 to 7 Do tavu:=(tavu shl 1)+bit_from_tape; tavut[laskuri]:=tavu; Writeln(tavu); Inc(laskuri); Until laskuri>=maara; End; Procedure print_header; Var a:integer; Begin Writeln; Write('Program: '); For a:=1 to 10 Do Begin Writhr(tavut[a])); End; Writeln; Writeln('Start: ',tavut[13]+256*tavut[14]); Writeln('Length: ',tavut[11]+256*tavut[12]); End; Procedure header_load; Begin load_bytes(17); print_header; End; { ************* Main program ************** } dummy:word; Begin CLI; old_bit:=0; dummy:=time_difference; Wait_loader_tone; header_load; STI; Repeat Until KeyPressed; End.
Tomi Engdahl <[email protected]>