1 // license:BSD-3-Clause
2 // copyright-holders:Sandro Ronco
3 /********************************************************************
4 
5     Support for VG-5000 .k7 cassette images
6 
7 ********************************************************************/
8 #include "vg5k_cas.h"
9 
10 #include <cassert>
11 
12 
13 #define SMPLO   -32768
14 #define SILENCE 0
15 #define SMPHI   32767
16 
17 
18 static int k7_size; // FIXME: global variable prevents multiple instances
19 
20 /*******************************************************************
21    Generate one high-low cycle of sample data
22 ********************************************************************/
vg5k_cas_cycle(int16_t * buffer,int sample_pos,int len)23 static inline int vg5k_cas_cycle(int16_t *buffer, int sample_pos, int len)
24 {
25 	int i = 0;
26 
27 	if (buffer)
28 	{
29 		while( i < len)
30 		{
31 			buffer[ sample_pos + i ] = SMPHI;
32 			i++;
33 		}
34 
35 		while( i < len * 2 )
36 		{
37 			buffer[ sample_pos + i ] = SMPLO;
38 			i++;
39 		}
40 	}
41 	return len * 2;
42 }
43 
44 
45 /*******************************************************************
46    Generate n samples of silence
47 ********************************************************************/
vg5k_cas_silence(int16_t * buffer,int sample_pos,int len)48 static inline int vg5k_cas_silence(int16_t *buffer, int sample_pos, int len)
49 {
50 	int i = 0;
51 
52 	if ( buffer )
53 		for( i = 0; i < len; i++)
54 			buffer[ sample_pos + i ] = SILENCE;
55 
56 	return len;
57 }
58 
59 
60 /*******************************************************************
61    Generate the end-byte samples
62 ********************************************************************/
vg5k_cas_eob(int16_t * buffer,int sample_pos)63 static inline int vg5k_cas_eob(int16_t *buffer, int sample_pos)
64 {
65 	int i, samples = 0;
66 
67 	for (i = 0; i < 4; i++)
68 		samples += vg5k_cas_cycle( buffer, sample_pos + samples, 5);
69 
70 	samples += vg5k_cas_cycle( buffer, sample_pos + samples, 10);
71 
72 	return samples;
73 }
74 
75 
vg5k_cas_byte(int16_t * buffer,int sample_pos,uint8_t data)76 static inline int vg5k_cas_byte(int16_t *buffer, int sample_pos, uint8_t data)
77 {
78 /* Writing an entire byte */
79 	int i, samples;
80 
81 	samples = 0;
82 	for ( i = 0; i < 8; i++ )
83 	{
84 		if ( data & 0x01 )
85 		{
86 			samples += vg5k_cas_cycle( buffer, sample_pos + samples, 5 );
87 			samples += vg5k_cas_cycle( buffer, sample_pos + samples, 5 );
88 		}
89 		else
90 		{
91 			samples += vg5k_cas_cycle( buffer, sample_pos + samples, 10 );
92 		}
93 
94 		data >>= 1;
95 	}
96 	return samples;
97 }
98 
99 
100 /*******************************************************************
101    Generate n sample of synchro
102 ********************************************************************/
vg5k_k7_synchro(int16_t * buffer,int sample_pos,int len)103 static inline int vg5k_k7_synchro(int16_t *buffer, int sample_pos, int len)
104 {
105 	int i, samples = 0;
106 
107 	for ( i = 0; i < len ; i++ )
108 			samples += vg5k_cas_cycle( buffer, sample_pos + samples, 5);
109 
110 	samples += vg5k_cas_eob( buffer, sample_pos + samples);
111 
112 	return samples;
113 }
114 
115 
vg5k_handle_tap(int16_t * buffer,const uint8_t * casdata)116 static int vg5k_handle_tap(int16_t *buffer, const uint8_t *casdata)
117 {
118 	int data_pos, sample_count;
119 
120 	data_pos = 0;
121 	sample_count = 0;
122 
123 	/* file has to start with an head block */
124 	if (casdata[0] != 0xd3  || casdata[1] != 0xd3 || casdata[2] != 0xd3)
125 		return -1;
126 
127 	/* on the entire file*/
128 	while( data_pos < k7_size )
129 	{
130 		uint16_t  block_size = 0;
131 
132 		/* Identify type of block */
133 		if (casdata[data_pos] == 0xd3)
134 		{
135 			/* head block have fixed size of 32 byte */
136 			block_size = 0x20;
137 
138 			/* 1 sec of silence before the head block */
139 			sample_count += vg5k_cas_silence(buffer, sample_count, 44100);
140 
141 			/* head block starts with 30000 samples of synchro */
142 			sample_count += vg5k_k7_synchro( buffer, sample_count, 30000 );
143 		}
144 		else if (casdata[data_pos] == 0xd6)
145 		{
146 			/* data block size is defined in head block */
147 			block_size = (casdata[data_pos - 4] | casdata[data_pos - 3]<<8) +  20;
148 
149 			/* 10000 samples of silence before the data block */
150 			sample_count += vg5k_cas_silence(buffer, sample_count, 10000);
151 
152 			/* data block starts with 7200 samples of synchro */
153 			sample_count += vg5k_k7_synchro( buffer, sample_count, 7200);
154 		}
155 		else
156 		{
157 			/* tries to handle files that do not respect the size declared in the head block */
158 			while (data_pos < k7_size && casdata[data_pos] != 0xd3 && casdata[data_pos] != 0xd6)
159 				data_pos++;
160 		}
161 
162 		/* Data samples */
163 		for ( ; block_size ; data_pos++, block_size-- )
164 		{
165 			/* Make sure there are enough bytes left */
166 			if (data_pos > k7_size)
167 				return -1;
168 
169 			sample_count += vg5k_cas_byte( buffer, sample_count, casdata[data_pos] );
170 
171 			/* generate the end-byte samples */
172 			sample_count += vg5k_cas_eob( buffer, sample_count);
173 		}
174 	}
175 
176 	/* Finish with 10000 samples of silence */
177 	sample_count += vg5k_cas_silence(buffer, sample_count, 10000);
178 
179 	return sample_count;
180 }
181 
182 
183 /*******************************************************************
184    Generate samples for the tape image
185 ********************************************************************/
vg5k_k7_fill_wave(int16_t * buffer,int sample_count,uint8_t * bytes)186 static int vg5k_k7_fill_wave(int16_t *buffer, int sample_count, uint8_t *bytes)
187 {
188 	return vg5k_handle_tap(buffer, bytes);
189 }
190 
191 
192 /*******************************************************************
193    Calculate the number of samples needed for this tape image classical
194 ********************************************************************/
vg5k_k7_to_wav_size(const uint8_t * casdata,int caslen)195 static int vg5k_k7_to_wav_size(const uint8_t *casdata, int caslen)
196 {
197 	k7_size = caslen ;
198 
199 	return vg5k_handle_tap( nullptr, casdata );
200 }
201 
202 
203 static const cassette_image::LegacyWaveFiller vg5k_legacy_fill_wave =
204 {
205 	vg5k_k7_fill_wave,                      /* fill_wave */
206 	-1,                                     /* chunk_size */
207 	0,                                      /* chunk_samples */
208 	vg5k_k7_to_wav_size,                    /* chunk_sample_calc */
209 	44100,                                  /* sample_frequency */
210 	0,                                      /* header_samples */
211 	0                                       /* trailer_samples */
212 };
213 
vg5k_k7_identify(cassette_image * cassette,cassette_image::Options * opts)214 static cassette_image::error vg5k_k7_identify(cassette_image *cassette, cassette_image::Options *opts)
215 {
216 	return cassette->legacy_identify(opts, &vg5k_legacy_fill_wave);
217 }
218 
219 
vg5k_k7_load(cassette_image * cassette)220 static cassette_image::error vg5k_k7_load(cassette_image *cassette)
221 {
222 	return cassette->legacy_construct(&vg5k_legacy_fill_wave);
223 }
224 
225 
226 static const cassette_image::Format vg5k_k7_format =
227 {
228 	"k7",
229 	vg5k_k7_identify,
230 	vg5k_k7_load,
231 	nullptr
232 };
233 
234 
235 CASSETTE_FORMATLIST_START(vg5k_cassette_formats)
236 	CASSETTE_FORMAT(vg5k_k7_format)
237 	CASSETTE_FORMAT(cassette_image::wavfile_format)
238 CASSETTE_FORMATLIST_END
239