(MESS) SOL20 : added partial support for SVT format (used in Solace emulator) [Robbbert]

This commit is contained in:
Robbbert 2014-03-04 11:57:46 +00:00
parent b50d6fff9d
commit 174b7aa57e
5 changed files with 430 additions and 40 deletions

2
.gitattributes vendored
View File

@ -2944,6 +2944,8 @@ src/lib/formats/sf7000_dsk.c svneol=native#text/plain
src/lib/formats/sf7000_dsk.h svneol=native#text/plain src/lib/formats/sf7000_dsk.h svneol=native#text/plain
src/lib/formats/smx_dsk.c svneol=native#text/plain src/lib/formats/smx_dsk.c svneol=native#text/plain
src/lib/formats/smx_dsk.h svneol=native#text/plain src/lib/formats/smx_dsk.h svneol=native#text/plain
src/lib/formats/sol_cas.c svneol=native#text/plain
src/lib/formats/sol_cas.h svneol=native#text/plain
src/lib/formats/sorc_cas.c svneol=native#text/plain src/lib/formats/sorc_cas.c svneol=native#text/plain
src/lib/formats/sorc_cas.h svneol=native#text/plain src/lib/formats/sorc_cas.h svneol=native#text/plain
src/lib/formats/sorc_dsk.c svneol=native#text/plain src/lib/formats/sorc_dsk.c svneol=native#text/plain

377
src/lib/formats/sol_cas.c Normal file
View File

@ -0,0 +1,377 @@
// license:BSD-3-Clause
// copyright-holders:Robbbert
/********************************************************************
Support for SOL-20 cassette images
SOL20 tapes consist of these sections:
1. A high tone whenever idle
2. A header
3. The data, in blocks of 256 bytes plus a CRC byte
4. The last block may be shorter, depending on the number of bytes
left to save.
Each byte has 1 start bit, 8 data bits (0-7), 2 stop bits.
The default speed is 1200 baud, which is what we emulate here.
A high bit is 1 cycle of 1200 Hz, while a low bit is half a cycle
of 600 Hz.
Formats:
SVT - The full explanation may be found on the Solace web site,
however this is a summary of what we support.
C (carrier) time in decaseconds
D (data bytes) in ascii text
H (header) tape header info
Multiple programs
Unsupported:
B (set baud rate) B 300 or B 1200
F load ENT file
S (silence) time in decaseconds
bad-byte symbols
escaped characters
********************************************************************/
#include "sol_cas.h"
#define WAVEENTRY_LOW -32768
#define WAVEENTRY_HIGH 32767
#define SOL20_WAV_FREQUENCY 4800
// image size
static UINT32 sol20_image_size;
static bool level;
static UINT8 sol20_cksm_byte;
static UINT32 sol20_byte_num;
static UINT8 sol20_header[16];
static int sol20_put_samples(INT16 *buffer, int sample_pos, int count)
{
if (buffer)
{
for (int i=0; i<count; i++)
buffer[sample_pos + i] = level ? WAVEENTRY_LOW : WAVEENTRY_HIGH;
level ^= 1;
}
return count;
}
static int sol20_output_bit(INT16 *buffer, int sample_pos, bool bit)
{
int samples = 0;
if (bit)
{
samples += sol20_put_samples(buffer, sample_pos + samples, 2);
samples += sol20_put_samples(buffer, sample_pos + samples, 2);
}
else
{
samples += sol20_put_samples(buffer, sample_pos + samples, 4);
}
return samples;
}
static int sol20_output_byte(INT16 *buffer, int sample_pos, UINT8 byte)
{
int samples = 0;
UINT8 i;
/* start */
samples += sol20_output_bit (buffer, sample_pos + samples, 0);
/* data */
for (i = 0; i<8; i++)
samples += sol20_output_bit (buffer, sample_pos + samples, (byte >> i) & 1);
/* stop */
for (i = 0; i<2; i++)
samples += sol20_output_bit (buffer, sample_pos + samples, 1);
return samples;
}
// Calculate checksum
static UINT8 sol20_calc_cksm(UINT8 cksm, UINT8 data)
{
data -= cksm;
cksm = data;
data ^= cksm;
data ^= 0xff;
data -= cksm;
return data;
}
// Ignore remainder of line
static void sol20_scan_to_eol(const UINT8 *bytes)
{
bool t = 1;
while (t)
{
if (sol20_byte_num >= sol20_image_size)
{
sol20_byte_num = 0;
t = 0;
}
else
if (bytes[sol20_byte_num] == 0x0d)
t = 0;
else
sol20_byte_num++;
}
}
// skip spaces and symbols looking for a hex digit
static void sol20_scan_to_hex(const UINT8 *bytes)
{
bool t = 1;
while (t)
{
if (sol20_byte_num >= sol20_image_size)
{
sol20_byte_num = 0;
t = 0;
}
else
{
UINT8 chr = bytes[sol20_byte_num];
if (chr == 0x0d)
t = 0;
else
if (((chr >= '0') && (chr <= '9')) || ((chr >= 'A') && (chr <= 'F')))
t = 0;
else
sol20_byte_num++;
}
}
}
// Turn n digits into hex
static int sol20_read_hex(const UINT8 *bytes, UINT8 numdigits)
{
int data = 0;
UINT8 i,chr;
for (i = 0; i < numdigits; i++)
{
chr = bytes[sol20_byte_num];
if ((chr >= '0') && (chr <= '9'))
{
data = (data << 4) | (chr-48);
sol20_byte_num++;
}
else
if ((chr >= 'A') && (chr <= 'F'))
{
data = (data << 4) | (chr-55);
sol20_byte_num++;
}
else
i = numdigits;
}
return data;
}
// Turn digits into decimal
static int sol20_read_dec(const UINT8 *bytes)
{
int data = 0;
while ((bytes[sol20_byte_num] >= '0') && (bytes[sol20_byte_num] <= '9'))
{
data = data*10 + bytes[sol20_byte_num] - 48;
sol20_byte_num++;
}
return data;
}
static int sol20_handle_cassette(INT16 *buffer, const UINT8 *bytes)
{
UINT32 sample_count = 0;
UINT32 i = 0,t = 0;
UINT8 c = 0;
sol20_byte_num = 1;
bool process_d = 0;
UINT16 length = 0;
// 1st line of file must say SVT
if ((bytes[0] == 'S') && (bytes[1] == 'V') && (bytes[2] == 'T'))
{ }
else
return sample_count;
// ignore remainder of line
sol20_scan_to_eol(bytes);
// process the commands
while (sol20_byte_num)
{
sol20_byte_num+=2; // bump to start of next line
UINT8 chr = bytes[sol20_byte_num]; // Get command
if (sol20_byte_num >= sol20_image_size)
sol20_byte_num = 0;
else
{
switch (chr)
{
case 0x0d:
break;
case 'C': // carrier
{
if (c) // if this is the next file, clean up after the previous one
{
sample_count += sol20_output_byte(buffer, sample_count, sol20_cksm_byte); // final checksum if needed
c = 0;
}
sol20_byte_num+=2; // bump to parameter
t = sol20_read_dec(bytes) * 140; // convert 10th of seconds to number of ones
for (i = 0; i < t; i++)
sample_count += sol20_output_bit(buffer, sample_count, 1);
sol20_scan_to_eol(bytes);
break;
}
case 'H': // header
{
if (c) // if this is the next file, clean up after the previous one
{
sample_count += sol20_output_byte(buffer, sample_count, sol20_cksm_byte); // final checksum if needed
c = 0;
}
sol20_byte_num+=2; // bump to file name
sol20_header[0] = bytes[sol20_byte_num++];
sol20_header[1] = bytes[sol20_byte_num++];
sol20_header[2] = bytes[sol20_byte_num++];
sol20_header[3] = bytes[sol20_byte_num++];
sol20_header[4] = bytes[sol20_byte_num++];
sol20_header[5] = 0;
sol20_scan_to_hex(bytes); // bump to file type
sol20_header[6] = sol20_read_hex(bytes, 2);
sol20_scan_to_hex(bytes); // bump to length
length = sol20_read_hex(bytes, 4);
sol20_header[7] = length;
sol20_header[8] = length >> 8;
sol20_scan_to_hex(bytes); // bump to load-address
i = sol20_read_hex(bytes, 4);
sol20_header[9] = i;
sol20_header[10] = i >> 8;
sol20_scan_to_hex(bytes); // bump to exec-address
i = sol20_read_hex(bytes, 4);
sol20_header[11] = i;
sol20_header[12] = i >> 8;
sol20_header[13] = 0;
sol20_header[14] = 0;
sol20_header[15] = 0;
sol20_cksm_byte = 0;
for (i = 0; i < 16; i++)
sol20_cksm_byte = sol20_calc_cksm(sol20_cksm_byte, sol20_header[i]);
// write leader
for (i = 0; i < 100; i++)
sample_count += sol20_output_byte(buffer, sample_count, 0);
// write SOH
sample_count += sol20_output_byte(buffer, sample_count, 1);
// write Header
for (i = 0; i < 16; i++)
sample_count += sol20_output_byte(buffer, sample_count, sol20_header[i]);
// write checksum
sample_count += sol20_output_byte(buffer, sample_count, sol20_cksm_byte);
sol20_cksm_byte = 0;
process_d = 1;
sol20_scan_to_eol(bytes);
break;
}
case 'D': // data
{
sol20_byte_num+=2; // bump to first byte
while ((bytes[sol20_byte_num] != 0x0d) && sol20_byte_num && process_d)
{
t = sol20_read_hex(bytes, 2);
sample_count += sol20_output_byte(buffer, sample_count, t);
sol20_cksm_byte = sol20_calc_cksm(sol20_cksm_byte, t);
c++;
length--;
if (!length)
process_d = 0;
if (!c)
{
sample_count += sol20_output_byte(buffer, sample_count, sol20_cksm_byte);
sol20_cksm_byte = 0;
}
sol20_scan_to_hex(bytes);
}
}
default: // everything else is ignored
sol20_scan_to_eol(bytes);
break;
}
}
}
if (c) // reached the end of the svt file
sample_count += sol20_output_byte(buffer, sample_count, sol20_cksm_byte); // final checksum if needed
return sample_count;
}
/*******************************************************************
Generate samples for the tape image
********************************************************************/
static int sol20_cassette_fill_wave(INT16 *buffer, int length, UINT8 *bytes)
{
return sol20_handle_cassette(buffer, bytes);
}
/*******************************************************************
Calculate the number of samples needed for this tape image
********************************************************************/
static int sol20_cassette_calculate_size_in_samples(const UINT8 *bytes, int length)
{
sol20_image_size = length;
return sol20_handle_cassette(NULL, bytes);
}
static const struct CassetteLegacyWaveFiller sol20_legacy_fill_wave =
{
sol20_cassette_fill_wave, /* fill_wave */
-1, /* chunk_size */
0, /* chunk_samples */
sol20_cassette_calculate_size_in_samples, /* chunk_sample_calc */
SOL20_WAV_FREQUENCY, /* sample_frequency */
0, /* header_samples */
0 /* trailer_samples */
};
static casserr_t sol20_cassette_identify(cassette_image *cassette, struct CassetteOptions *opts)
{
return cassette_legacy_identify(cassette, opts, &sol20_legacy_fill_wave);
}
static casserr_t sol20_cassette_load(cassette_image *cassette)
{
return cassette_legacy_construct(cassette, &sol20_legacy_fill_wave);
}
static const struct CassetteFormat sol20_cassette_image_format =
{
"svt",
sol20_cassette_identify,
sol20_cassette_load,
NULL
};
CASSETTE_FORMATLIST_START(sol20_cassette_formats)
CASSETTE_FORMAT(sol20_cassette_image_format)
CASSETTE_FORMATLIST_END

15
src/lib/formats/sol_cas.h Normal file
View File

@ -0,0 +1,15 @@
// license:BSD-3-Clause
// copyright-holders:Robbbert
/*********************************************************************
sol_cas.h
Format code for SOL-20 cassette images
*********************************************************************/
#include "cassimg.h"
CASSETTE_FORMATLIST_EXTERN(sol20_cassette_formats);

View File

@ -181,6 +181,7 @@ FORMATSOBJS = \
$(LIBOBJ)/formats/sc3000_bit.o \ $(LIBOBJ)/formats/sc3000_bit.o \
$(LIBOBJ)/formats/sf7000_dsk.o \ $(LIBOBJ)/formats/sf7000_dsk.o \
$(LIBOBJ)/formats/smx_dsk.o \ $(LIBOBJ)/formats/smx_dsk.o \
$(LIBOBJ)/formats/sol_cas.o \
$(LIBOBJ)/formats/sorc_dsk.o \ $(LIBOBJ)/formats/sorc_dsk.o \
$(LIBOBJ)/formats/sorc_cas.o \ $(LIBOBJ)/formats/sorc_cas.o \
$(LIBOBJ)/formats/sord_cas.o \ $(LIBOBJ)/formats/sord_cas.o \

View File

@ -71,9 +71,8 @@
correct version of BASIC be loaded first. Paste works, but it is very correct version of BASIC be loaded first. Paste works, but it is very
very slow. Perhaps we need something faster such as what Solace has. very slow. Perhaps we need something faster such as what Solace has.
- SVT (Solace Virtual Tape) files are a representation of a cassette, - SVT (Solace Virtual Tape) files are a representation of a cassette,
usually holding about 4 games, just like a multifile tape. It will usually holding about 4 games, just like a multifile tape. This format
need a 'format' program to be written to convert it to be loadable is partially supported.
via the cassette device.
- HEX files appear to be the standard Intel format, and can be loaded - HEX files appear to be the standard Intel format, and can be loaded
by Solace. by Solace.
- The remaining formats (OPN, PL, PRN, SMU, SOL, ASM and LIB) appear - The remaining formats (OPN, PL, PRN, SMU, SOL, ASM and LIB) appear
@ -107,6 +106,7 @@
#include "sound/wave.h" #include "sound/wave.h"
#include "imagedev/cassette.h" #include "imagedev/cassette.h"
#include "machine/ay31015.h" #include "machine/ay31015.h"
#include "formats/sol_cas.h"
struct cass_data_t { struct cass_data_t {
@ -134,23 +134,22 @@ public:
}; };
sol20_state(const machine_config &mconfig, device_type type, const char *tag) sol20_state(const machine_config &mconfig, device_type type, const char *tag)
: driver_device(mconfig, type, tag), : driver_device(mconfig, type, tag)
m_maincpu(*this, "maincpu"), , m_maincpu(*this, "maincpu")
m_cass1(*this, "cassette"), , m_cass1(*this, "cassette")
m_cass2(*this, "cassette2"), , m_cass2(*this, "cassette2")
m_uart(*this, "uart"), , m_uart(*this, "uart")
m_uart_s(*this, "uart_s"), , m_uart_s(*this, "uart_s")
m_p_videoram(*this, "videoram"), , m_p_videoram(*this, "videoram")
m_iop_arrows(*this, "ARROWS"), , m_iop_arrows(*this, "ARROWS")
m_iop_config(*this, "CONFIG"), , m_iop_config(*this, "CONFIG")
m_iop_s1(*this, "S1"), , m_iop_s1(*this, "S1")
m_iop_s2(*this, "S2"), , m_iop_s2(*this, "S2")
m_iop_s3(*this, "S3"), , m_iop_s3(*this, "S3")
m_iop_s4(*this, "S4"), , m_iop_s4(*this, "S4")
m_cassette1(*this, "cassette"), , m_cassette1(*this, "cassette")
m_cassette2(*this, "cassette2") , m_cassette2(*this, "cassette2")
{ { }
}
DECLARE_READ8_MEMBER( sol20_f8_r ); DECLARE_READ8_MEMBER( sol20_f8_r );
DECLARE_READ8_MEMBER( sol20_f9_r ); DECLARE_READ8_MEMBER( sol20_f9_r );
@ -165,12 +164,24 @@ public:
DECLARE_WRITE8_MEMBER( sol20_fd_w ); DECLARE_WRITE8_MEMBER( sol20_fd_w );
DECLARE_WRITE8_MEMBER( sol20_fe_w ); DECLARE_WRITE8_MEMBER( sol20_fe_w );
DECLARE_WRITE8_MEMBER( kbd_put ); DECLARE_WRITE8_MEMBER( kbd_put );
DECLARE_DRIVER_INIT(sol20);
TIMER_CALLBACK_MEMBER(sol20_cassette_tc);
TIMER_CALLBACK_MEMBER(sol20_boot);
UINT32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect);
private:
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr);
UINT8 m_sol20_fa; UINT8 m_sol20_fa;
cass_data_t m_cass_data;
virtual void machine_reset(); virtual void machine_reset();
virtual void machine_start(); virtual void machine_start();
virtual void video_start(); virtual void video_start();
UINT32 screen_update(screen_device &screen, bitmap_ind16 &bitmap, const rectangle &cliprect); UINT8 m_sol20_fc;
UINT8 m_sol20_fe;
const UINT8 *m_p_chargen;
UINT8 m_framecnt;
cass_data_t m_cass_data;
emu_timer *m_cassette_timer;
cassette_image_device *cassette_device_image();
required_device<cpu_device> m_maincpu; required_device<cpu_device> m_maincpu;
required_device<cassette_image_device> m_cass1; required_device<cassette_image_device> m_cass1;
required_device<cassette_image_device> m_cass2; required_device<cassette_image_device> m_cass2;
@ -183,24 +194,8 @@ public:
required_ioport m_iop_s2; required_ioport m_iop_s2;
required_ioport m_iop_s3; required_ioport m_iop_s3;
required_ioport m_iop_s4; required_ioport m_iop_s4;
private:
UINT8 m_sol20_fc;
UINT8 m_sol20_fe;
const UINT8 *m_p_chargen;
UINT8 m_framecnt;
emu_timer *m_cassette_timer;
required_device<cassette_image_device> m_cassette1; required_device<cassette_image_device> m_cassette1;
required_device<cassette_image_device> m_cassette2; required_device<cassette_image_device> m_cassette2;
public:
DECLARE_DRIVER_INIT(sol20);
TIMER_CALLBACK_MEMBER(sol20_cassette_tc);
TIMER_CALLBACK_MEMBER(sol20_boot);
cassette_image_device *cassette_device_image();
protected:
virtual void device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr);
}; };
@ -564,9 +559,9 @@ static const ay31015_config sol20_ay31015_config =
static const cassette_interface sol20_cassette_interface = static const cassette_interface sol20_cassette_interface =
{ {
cassette_default_formats, sol20_cassette_formats,//cassette_default_formats,
NULL, NULL,
(cassette_state)(CASSETTE_PLAY | CASSETTE_MOTOR_DISABLED | CASSETTE_SPEAKER_ENABLED), (cassette_state)(CASSETTE_PLAY | CASSETTE_MOTOR_ENABLED | CASSETTE_SPEAKER_ENABLED),
NULL, NULL,
NULL NULL
}; };