summaryrefslogtreecommitdiffstatshomepage
path: root/src/lib/formats/mbee_cas.cpp
blob: 24024f66f5d8a0a5a7b0dd8271cc110ec742a5e7 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
// license:BSD-3-Clause
// copyright-holders:Robbbert
/********************************************************************

Support for Microbee cassette images

Microbee tapes consist of 3 sections
1. A leader of 63 zeroes
2. A header which contains the program name and other info
3. The main program

Each byte after conversion becomes a start bit, bit 0,1,etc to 7,
then 2 stop bits.

At 1200 baud, a high = 2 cycles of 2400Hz and a low = 1 cycle of 1200Hz
At 300 baud, a high = 8 cycles of 2400Hz and a low = 4 cycles of 1200Hz

The header bytes are arranged thus:
1 (SOH) 0x01
6 File name
1 file type (M=machine language, B=Basic)
2 length
2 load address
2 exec address
1 tape speed (0 = 300 baud; other = 1200 baud)
1 auto-start (0 = no)
1 unassigned byte
1 CRC byte

The header is always at 300 baud; the program will be at the
speed indicated by the speed byte.

By coincidence (or not), the header is the same format as that
of the Sorcerer and SOL-20. In these, the speed and auto-start
bytes are unassigned. The CRC uses the same algorithm.

The main program is broken into blocks of 256, with each block
having its own CRC byte.

Microbee tape and quickload formats:

BEE - straight binary dump to address 0900, no header. For Machine
      Language programs.

BIN - the standard z80bin format.

COM - straight binary dump to address 0100, no header. For Machine
      Language programs.

MWB - straight binary dump to address 08C0, no header. For BASIC
      programs.

TAP - has an ID header of TAP_DGOS_BEE or MBEE, null terminated.
      This is followed by the binary dump with the leader and CRC
      bytes included.

********************************************************************/

#include <assert.h>

#include "mbee_cas.h"

#define WAVEENTRY_LOW  -32768
#define WAVEENTRY_HIGH  32767

#define MBEE_WAV_FREQUENCY   9600

// image size
static int mbee_image_size;
static bool mbee_speed;

static int mbee_put_samples(int16_t *buffer, int sample_pos, int count, int level)
{
	if (buffer)
	{
		for (int i=0; i<count; i++)
			buffer[sample_pos + i] = level;
	}

	return count;
}

static int mbee_output_bit(int16_t *buffer, int sample_pos, bool bit)
{
	int samples = 0;
	if (mbee_speed)
	{
		if (bit)
		{
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
		}
		else
		{
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_HIGH);
		}
	}
	else
	{
		if (bit)
		{
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 2, WAVEENTRY_HIGH);
		}
		else
		{
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_HIGH);
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_LOW);
			samples += mbee_put_samples(buffer, sample_pos + samples, 4, WAVEENTRY_HIGH);
		}
	}

	return samples;
}

static int mbee_output_byte(int16_t *buffer, int sample_pos, uint8_t byte)
{
	int samples = 0;
	uint8_t i;

	/* start */
	samples += mbee_output_bit (buffer, sample_pos + samples, 0);

	/* data */
	for (i = 0; i<8; i++)
		samples += mbee_output_bit (buffer, sample_pos + samples, (byte >> i) & 1);

	/* stop */
	for (i = 0; i<2; i++)
		samples += mbee_output_bit (buffer, sample_pos + samples, 1);

	return samples;
}

static int mbee_handle_tap(int16_t *buffer, const uint8_t *bytes)
{
	uint32_t sample_count = 0;
	uint32_t byte_count = 0;
	uint32_t i = 0;
	bool temp_speed = 0;
	uint8_t temp_blocks = 0;
	uint16_t temp_size = 0;

	// TAP file starts with a null-terminate ID string. We just skip this.
	while (bytes[byte_count])
		byte_count++;

	// there can be a library of files, loop through them all
	while (byte_count < mbee_image_size)
	{
		mbee_speed = 0;

		// now output the leader
		while ( (!bytes[byte_count]) && (byte_count < mbee_image_size) )
			sample_count += mbee_output_byte(buffer, sample_count, bytes[byte_count++]);

		// make sure SOH is where we expect
		if (bytes[byte_count] != 1 )
			break;

		// store the size for later
		temp_blocks = bytes[byte_count + 9];
		temp_size = bytes[byte_count + 8] + temp_blocks*256;

		// store the speed for later
		temp_speed = (bytes[byte_count + 15]) ? 1 : 0;

		/* header */
		for (i=0; i<18; i++)
			sample_count += mbee_output_byte(buffer, sample_count, bytes[byte_count++]);

		// change speed
		mbee_speed = temp_speed;

		// calculate size of program including CRC bytes
		temp_size = temp_size + temp_blocks + 1;

		/* data */
		for (i=0; i<temp_size; i++)
			sample_count += mbee_output_byte(buffer, sample_count, bytes[byte_count++]);

	}

	return sample_count;
}


/*******************************************************************
   Generate samples for the tape image
********************************************************************/

static int mbee_tap_fill_wave(int16_t *buffer, int length, uint8_t *bytes)
{
	return mbee_handle_tap(buffer, bytes);
}

/*******************************************************************
   Calculate the number of samples needed for this tape image
********************************************************************/

static int mbee_tap_calculate_size_in_samples(const uint8_t *bytes, int length)
{
	mbee_image_size = length;

	return mbee_handle_tap(nullptr, bytes);
}

static const struct CassetteLegacyWaveFiller mbee_tap_config =
{
	mbee_tap_fill_wave,                 /* fill_wave */
	-1,                                     /* chunk_size */
	0,                                      /* chunk_samples */
	mbee_tap_calculate_size_in_samples, /* chunk_sample_calc */
	MBEE_WAV_FREQUENCY,                      /* sample_frequency */
	0,                                      /* header_samples */
	0                                       /* trailer_samples */
};

static cassette_image::error mbee_tap_identify(cassette_image *cassette, struct CassetteOptions *opts)
{
	return cassette_legacy_identify(cassette, opts, &mbee_tap_config);
}

static cassette_image::error mbee_tap_load(cassette_image *cassette)
{
	return cassette_legacy_construct(cassette, &mbee_tap_config);
}

static const struct CassetteFormat mbee_tap_image_format =
{
	"tap",
	mbee_tap_identify,
	mbee_tap_load,
	nullptr
};

CASSETTE_FORMATLIST_START(mbee_cassette_formats)
	CASSETTE_FORMAT(mbee_tap_image_format)
CASSETTE_FORMATLIST_END