2016-07-10 14:17:53 +00:00
//
// G64.cpp
// Clock Signal
//
// Created by Thomas Harte on 10/07/2016.
// Copyright © 2016 Thomas Harte. All rights reserved.
//
# include "G64.hpp"
using namespace Storage ;
G64 : : G64 ( const char * file_name )
{
_file = fopen ( file_name , " rb " ) ;
if ( ! _file )
throw ErrorNotGCR ;
// read and check the file signature
char signature [ 8 ] ;
2016-07-10 17:32:59 +00:00
if ( fread ( signature , 1 , 8 , _file ) ! = 8 )
2016-07-10 14:17:53 +00:00
throw ErrorNotGCR ;
if ( memcmp ( signature , " GCR-1541 " , 8 ) )
throw ErrorNotGCR ;
// check the version number
int version = fgetc ( _file ) ;
if ( version ! = 0 )
{
throw ErrorUnknownVersion ;
}
// get the number of tracks and track size
_number_of_tracks = ( uint8_t ) fgetc ( _file ) ;
_maximum_track_size = ( uint16_t ) fgetc ( _file ) ;
_maximum_track_size | = ( uint16_t ) fgetc ( _file ) < < 8 ;
2016-07-10 22:07:53 +00:00
get_track_at_position ( 0 ) ;
2016-07-10 14:17:53 +00:00
}
G64 : : ~ G64 ( )
{
if ( _file ) fclose ( _file ) ;
}
unsigned int G64 : : get_head_position_count ( )
{
// give at least 84 tracks, to yield the normal geometry but,
// if there are more, shove them in
return _number_of_tracks > 84 ? _number_of_tracks : 84 ;
}
std : : shared_ptr < Track > G64 : : get_track_at_position ( unsigned int position )
{
std : : shared_ptr < Track > resulting_track ;
// if there's definitely no track here, return the empty track
// (TODO: should be supplying one with an index hole?)
if ( position > = _number_of_tracks ) return resulting_track ;
// seek to this track's entry in the track table
2016-07-10 17:42:45 +00:00
fseek ( _file , ( long ) ( ( position * 4 ) + 0xc ) , SEEK_SET ) ;
2016-07-10 14:17:53 +00:00
// read the track offset
uint32_t track_offset ;
track_offset = ( uint32_t ) fgetc ( _file ) ;
track_offset | = ( uint32_t ) fgetc ( _file ) < < 8 ;
track_offset | = ( uint32_t ) fgetc ( _file ) < < 16 ;
track_offset | = ( uint32_t ) fgetc ( _file ) < < 24 ;
// if the track offset is zero, this track doesn't exist, so...
if ( ! track_offset ) return resulting_track ;
// seek to the track start
2016-07-10 17:42:45 +00:00
fseek ( _file , ( int ) track_offset , SEEK_SET ) ;
2016-07-10 14:17:53 +00:00
// get the real track length
uint16_t track_length ;
track_length = ( uint16_t ) fgetc ( _file ) ;
track_length | = ( uint16_t ) fgetc ( _file ) < < 8 ;
// grab the byte contents of this track
2016-07-10 20:10:05 +00:00
std : : unique_ptr < uint8_t > track_contents ( new uint8_t [ track_length ] ) ;
fread ( track_contents . get ( ) , 1 , track_length , _file ) ;
2016-07-10 14:17:53 +00:00
2016-07-10 17:42:45 +00:00
// seek to this track's entry in the speed zone table
fseek ( _file , ( long ) ( ( position * 4 ) + 0x15c ) , SEEK_SET ) ;
2016-07-10 14:17:53 +00:00
2016-07-10 17:42:45 +00:00
// read the speed zone offsrt
uint32_t speed_zone_offset ;
speed_zone_offset = ( uint32_t ) fgetc ( _file ) ;
speed_zone_offset | = ( uint32_t ) fgetc ( _file ) < < 8 ;
speed_zone_offset | = ( uint32_t ) fgetc ( _file ) < < 16 ;
speed_zone_offset | = ( uint32_t ) fgetc ( _file ) < < 24 ;
// if the speed zone is not constant, create a track based on the whole table; otherwise create one that's constant
if ( speed_zone_offset > 3 )
{
// seek to start of speed zone
fseek ( _file , ( int ) speed_zone_offset , SEEK_SET ) ;
uint16_t speed_zone_length = ( track_length + 3 ) > > 2 ;
// read the speed zone bytes
uint8_t speed_zone_contents [ speed_zone_length ] ;
fread ( speed_zone_contents , 1 , speed_zone_length , _file ) ;
2016-07-10 20:10:05 +00:00
2016-07-10 22:07:53 +00:00
// divide track into appropriately timed PCMSegments
std : : vector < PCMSegment > segments ;
unsigned int current_speed = speed_zone_contents [ 0 ] > > 6 ;
unsigned int start_byte_in_current_speed = 0 ;
for ( unsigned int byte = 0 ; byte < track_length ; byte + + )
{
unsigned int byte_speed = speed_zone_contents [ byte > > 2 ] > > ( 6 - ( byte & 3 ) * 2 ) ;
if ( byte_speed ! = current_speed | | byte = = ( track_length - 1 ) )
{
unsigned int number_of_bytes = byte - start_byte_in_current_speed ;
PCMSegment segment ;
segment . duration . length = number_of_bytes * 8 ;
2016-07-10 22:24:12 +00:00
segment . duration . clock_rate = 4000000 / ( 13 + current_speed ) ; // the speed zone divides a 4Mhz clock by 13, 14, 15 or 16; TODO: is this the right way around? Is zone 3 the fastest or the slowest?
2016-07-10 22:07:53 +00:00
segment . data . reset ( new uint8_t [ number_of_bytes ] ) ;
memcpy ( segment . data . get ( ) , & track_contents . get ( ) [ start_byte_in_current_speed ] , number_of_bytes ) ;
segments . push_back ( std : : move ( segment ) ) ;
current_speed = byte_speed ;
start_byte_in_current_speed = byte ;
}
}
resulting_track . reset ( new PCMTrack ( std : : move ( segments ) ) ) ;
2016-07-10 17:42:45 +00:00
}
else
{
2016-07-10 20:10:05 +00:00
PCMSegment segment ;
segment . duration . length = track_length * 8 ;
2016-07-10 22:07:53 +00:00
segment . duration . clock_rate = 1 ; // this is arbitrary; if supplying only one PCMSegment then it'll naturally fill the track
2016-07-10 20:10:05 +00:00
segment . data = std : : move ( track_contents ) ;
resulting_track . reset ( new PCMTrack ( std : : move ( segment ) ) ) ;
2016-07-10 17:42:45 +00:00
}
2016-07-10 14:17:53 +00:00
2016-07-10 22:24:12 +00:00
// TODO: find out whether it's possible for a G64 to supply only a partial track. I don't think it is, which would make the
// above correct but supposing I'm wrong, the above would produce some incorrectly clocked tracks
2016-07-10 14:17:53 +00:00
return resulting_track ;
}
2016-07-10 20:10:05 +00:00
# pragma mark - PCMTrack
unsigned int greatest_common_divisor ( unsigned int a , unsigned int b )
{
if ( a < b )
{
unsigned int swap = b ;
b = a ;
a = swap ;
}
while ( 1 ) {
if ( ! a ) return b ;
if ( ! b ) return a ;
unsigned int remainder = a % b ;
a = b ;
b = remainder ;
}
}
unsigned int least_common_multiple ( unsigned int a , unsigned int b )
{
unsigned int gcd = greatest_common_divisor ( a , b ) ;
return ( a * b ) / gcd ;
}
PCMTrack : : PCMTrack ( std : : vector < PCMSegment > segments )
{
_segments = std : : move ( segments ) ;
fix_length ( ) ;
}
PCMTrack : : PCMTrack ( PCMSegment segment )
{
_segments . push_back ( std : : move ( segment ) ) ;
fix_length ( ) ;
}
PCMTrack : : Event PCMTrack : : get_next_event ( )
{
2016-07-10 20:17:25 +00:00
// find the next 1 in the input stream, keeping count of length as we go, and assuming it's going
// to be a flux transition
_next_event . type = Track : : Event : : FluxTransition ;
_next_event . length . length = 0 ;
while ( _segment_pointer < _segments . size ( ) )
{
unsigned int clock_multiplier = _track_clock_rate / _segments [ _segment_pointer ] . duration . clock_rate ;
const uint8_t * segment_data = _segments [ _segment_pointer ] . data . get ( ) ;
while ( _bit_pointer < _segments [ _segment_pointer ] . duration . length )
{
// for timing simplicity, bits are modelled as happening at the end of their window
2016-07-10 20:21:52 +00:00
// TODO: should I account for the converse bit ordering? Or can I assume MSB first?
int bit = segment_data [ _bit_pointer > > 3 ] & ( 0x80 > > ( _bit_pointer & 7 ) ) ;
2016-07-10 20:17:25 +00:00
_bit_pointer + + ;
_next_event . length . length + = clock_multiplier ;
if ( bit ) return _next_event ;
}
_bit_pointer = 0 ;
_segment_pointer + + ;
}
// check whether we actually reached the index hole
if ( _segment_pointer = = _segments . size ( ) )
{
_segment_pointer = 0 ;
_next_event . type = Track : : Event : : IndexHole ;
}
return _next_event ;
2016-07-10 20:10:05 +00:00
}
void PCMTrack : : fix_length ( )
{
// find the least common multiple of all segment clock rates
_track_clock_rate = _segments [ 0 ] . duration . clock_rate ;
for ( size_t c = 1 ; c < _segments . size ( ) ; c + + )
{
_track_clock_rate = least_common_multiple ( _track_clock_rate , _segments [ c ] . duration . clock_rate ) ;
}
// therby determine the total length, storing it to next_event as the divisor
_next_event . length . clock_rate = 0 ;
for ( size_t c = 0 ; c < _segments . size ( ) ; c + + )
{
unsigned int multiplier = _track_clock_rate / _segments [ c ] . duration . clock_rate ;
_next_event . length . clock_rate + = _segments [ c ] . duration . length * multiplier ;
}
_segment_pointer = _bit_pointer = 0 ;
}