1 // license:BSD-3-Clause
2 // copyright-holders:Wilbert Pol
3 /********************************************************************
4
5 Support for EACA Colour Genie .cas cassette images
6
7 Current state: Not working. Only the sync signal and 0x66 byte get
8 recognized.
9
10 NOTE: There exist multiples type of .cas files for Colour Genie
11 - the original one from Jurgen's emu, which starts with TAPE_HEADER
12 below, followed by the sync signal, without the 255 leading 0xaa
13 bytes (which are added at loading time)
14 - a newer type from Genieous emu, which does not start with TAPE_HEADER
15 but contains the 255 leading 0xaa bytes (which are now skipped below)
16 - an alternative type (from Genieous as well?) without TAPE_HEADER
17 and without the 255 leading 0xaa bytes
18 We now support these three types below...
19
20 ********************************************************************/
21 #include "formats/cgen_cas.h"
22
23 #include <cassert>
24
25
26 #define TAPE_HEADER "Colour Genie - Virtual Tape File"
27
28 #define SMPLO -32768
29 #define SMPHI 32767
30
31
32 static int cas_size;
33 static int level;
34
35
cgenie_output_byte(int16_t * buffer,int sample_count,uint8_t data)36 static int cgenie_output_byte(int16_t *buffer, int sample_count, uint8_t data)
37 {
38 int samples = 0;
39
40 for (int i = 0; i < 8; i++)
41 {
42 // Output bit boundary
43 level ^= 1;
44 if (buffer)
45 buffer[sample_count + samples] = level ? SMPHI : SMPLO;
46 samples++;
47
48 // Output bit
49 if (data & 0x80)
50 level ^= 1;
51 if (buffer)
52 buffer[sample_count + samples] = level ? SMPHI : SMPLO;
53 samples++;
54
55 data <<= 1;
56 }
57 return samples;
58 }
59
60
cgenie_handle_cas(int16_t * buffer,const uint8_t * casdata)61 static int cgenie_handle_cas(int16_t *buffer, const uint8_t *casdata)
62 {
63 int data_pos, sample_count;
64
65 data_pos = 0;
66 sample_count = 0;
67 level = 0;
68
69 // Check for presence of optional header
70 if (!memcmp(casdata, TAPE_HEADER, sizeof(TAPE_HEADER) - 1))
71 {
72 // Search for 0x00 or end of file
73 while (data_pos < cas_size && casdata[data_pos])
74 data_pos++;
75
76 // If we're at the end of the file it's not a valid .cas file
77 if (data_pos == cas_size)
78 return -1;
79
80 // Skip the 0x00 byte
81 data_pos++;
82 }
83
84 // If we're at the end of the file it's not a valid .cas file
85 if (data_pos == cas_size)
86 return -1;
87
88 // Check for beginning of tape file marker (possibly skipping the 0xaa header)
89 if (casdata[data_pos] != 0x66 && casdata[data_pos + 0xff] != 0x66)
90 return -1;
91
92 // Create header, if not present in the file
93 if (casdata[data_pos] == 0x66)
94 for (int i = 0; i < 256; i++)
95 sample_count += cgenie_output_byte(buffer, sample_count, 0xaa);
96
97 // Start outputting data
98 while (data_pos < cas_size)
99 {
100 sample_count += cgenie_output_byte(buffer, sample_count, casdata[data_pos]);
101 data_pos++;
102 }
103 sample_count += cgenie_output_byte(buffer, sample_count, 0x00);
104
105 return sample_count;
106 }
107
108 /*******************************************************************
109 Generate samples for the tape image
110 ********************************************************************/
cgenie_cas_fill_wave(int16_t * buffer,int sample_count,uint8_t * bytes)111 static int cgenie_cas_fill_wave(int16_t *buffer, int sample_count, uint8_t *bytes)
112 {
113 return cgenie_handle_cas(buffer, bytes);
114 }
115
116
117 /*******************************************************************
118 Calculate the number of samples needed for this tape image
119 ********************************************************************/
cgenie_cas_to_wav_size(const uint8_t * casdata,int caslen)120 static int cgenie_cas_to_wav_size(const uint8_t *casdata, int caslen)
121 {
122 cas_size = caslen;
123
124 return cgenie_handle_cas(nullptr, casdata);
125 }
126
127 static const cassette_image::LegacyWaveFiller cgenie_cas_legacy_fill_wave =
128 {
129 cgenie_cas_fill_wave, /* fill_wave */
130 -1, /* chunk_size */
131 0, /* chunk_samples */
132 cgenie_cas_to_wav_size, /* chunk_sample_calc */
133 2400, /* sample_frequency */
134 0, /* header_samples */
135 0 /* trailer_samples */
136 };
137
138
cgenie_cas_identify(cassette_image * cassette,cassette_image::Options * opts)139 static cassette_image::error cgenie_cas_identify(cassette_image *cassette, cassette_image::Options *opts)
140 {
141 return cassette->legacy_identify(opts, &cgenie_cas_legacy_fill_wave);
142 }
143
144
cgenie_cas_load(cassette_image * cassette)145 static cassette_image::error cgenie_cas_load(cassette_image *cassette)
146 {
147 return cassette->legacy_construct(&cgenie_cas_legacy_fill_wave);
148 }
149
150
151 static const cassette_image::Format cgenie_cas_format =
152 {
153 "cas",
154 cgenie_cas_identify,
155 cgenie_cas_load,
156 nullptr
157 };
158
159
160 CASSETTE_FORMATLIST_START(cgenie_cassette_formats)
161 CASSETTE_FORMAT(cgenie_cas_format)
162 CASSETTE_FORMATLIST_END
163