284 lines
8.0 KiB
C++
284 lines
8.0 KiB
C++
#include "zx_tape.h"
|
|
#include "zx_ula.h"
|
|
#include "zx_screen.h"
|
|
#include "zx_mem.h"
|
|
#include "z80debug.h"
|
|
#include "z80.h"
|
|
#include <vector>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
namespace zx_tape
|
|
{
|
|
#define PULSE_PILOT 0
|
|
#define PULSE_SYNC1 1
|
|
#define PULSE_SYNC2 2
|
|
#define PULSE_DATA 3
|
|
#define PULSE_CHECKSUM 4
|
|
#define PULSE_SYNC3 5
|
|
#define PULSE_WAIT 6
|
|
|
|
#define PULSE_LEN_ZERO 855
|
|
#define PULSE_LEN_ONE 1710
|
|
|
|
#define PULSE_LEN_PILOT 2168
|
|
#define PULSE_LEN_SYNC1 667
|
|
#define PULSE_LEN_SYNC2 735
|
|
#define PULSE_LEN_SYNC3 954
|
|
|
|
#define PULSE_COUNT_PILOT_HEADER 8063
|
|
#define PULSE_COUNT_PILOT_DATA 3223
|
|
|
|
struct block_t
|
|
{
|
|
uint16_t length;
|
|
uint8_t *data;
|
|
};
|
|
|
|
bool playing = false;
|
|
bool loaded = false;
|
|
bool options[ZXTAPE_NUM_OPTIONS] = { true, true };
|
|
|
|
std::vector<block_t> blocks;
|
|
uint8_t current_block = 0;
|
|
uint16_t block_pos=0;
|
|
uint8_t current_bit=0;
|
|
|
|
uint8_t current_section = PULSE_PILOT;
|
|
uint16_t current_pulse = 0;
|
|
uint32_t pulse_pos = 0;
|
|
uint8_t pulse_level = 0;
|
|
|
|
void load(const char* filename)
|
|
{
|
|
if (!blocks.empty()) for (auto block : blocks) free(block.data);
|
|
blocks.clear();
|
|
|
|
FILE *f = fopen(filename, "rb");
|
|
if (!f) return;
|
|
while (!feof(f))
|
|
{
|
|
block_t block;
|
|
fread(&block.length, 2, 1, f);
|
|
//fread(&block.flag, 1, 1, f);
|
|
//block.length -= 2; // substract flag and checksum
|
|
block.data = (uint8_t *)malloc(block.length);
|
|
fread(block.data, block.length, 1, f);
|
|
//fread(&block.checksum, 1, 1, f);
|
|
blocks.push_back(block);
|
|
}
|
|
fclose(f);
|
|
|
|
loaded = true;
|
|
playing = false;
|
|
rewind();
|
|
}
|
|
|
|
void play()
|
|
{
|
|
if (!loaded) return;
|
|
playing = !playing; //true;
|
|
}
|
|
|
|
void stop()
|
|
{
|
|
playing = false;
|
|
//berserk_mode = false;
|
|
}
|
|
|
|
void rewind()
|
|
{
|
|
if (!loaded) return;
|
|
current_block = block_pos = current_bit = 0;
|
|
current_section = PULSE_PILOT;
|
|
current_pulse = pulse_pos = 0;
|
|
pulse_level = 1;
|
|
}
|
|
|
|
void update(const uint8_t dt)
|
|
{
|
|
if (!playing) return;
|
|
|
|
pulse_pos += dt;
|
|
|
|
if (current_section == PULSE_PILOT)
|
|
{
|
|
const uint16_t pulse_count = blocks[current_block].data[0]<128 ? PULSE_COUNT_PILOT_HEADER : PULSE_COUNT_PILOT_DATA;
|
|
if (pulse_pos >= PULSE_LEN_PILOT )
|
|
{
|
|
pulse_pos -= PULSE_LEN_PILOT;
|
|
current_pulse++;
|
|
pulse_level = pulse_level?0:1;
|
|
if (current_pulse == pulse_count && pulse_level==0)
|
|
{
|
|
pulse_level = 0;
|
|
}
|
|
else if (current_pulse >= pulse_count)
|
|
{
|
|
current_pulse = 0;
|
|
pulse_level = 1;
|
|
current_section = PULSE_SYNC1;
|
|
printf("going to pulse_sync1..%i.\n", current_block);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (current_section == PULSE_SYNC1)
|
|
{
|
|
if (pulse_pos >= PULSE_LEN_SYNC1)
|
|
{
|
|
pulse_pos -= PULSE_LEN_SYNC1;
|
|
pulse_level = 0;
|
|
current_section = PULSE_SYNC2;
|
|
printf("going to pulse_sync2..%i.\n", current_block);
|
|
}
|
|
}
|
|
|
|
if (current_section == PULSE_SYNC2)
|
|
{
|
|
if (pulse_pos >= PULSE_LEN_SYNC2)
|
|
{
|
|
pulse_pos -= PULSE_LEN_SYNC2;
|
|
pulse_level = 1;
|
|
current_section = PULSE_DATA;
|
|
printf("going to pulse_data..%i.\n", current_block);
|
|
}
|
|
}
|
|
static int level[2] = {0, 0};
|
|
if (current_section == PULSE_DATA)
|
|
{
|
|
level[pulse_level]+=dt;
|
|
const uint8_t datum = blocks[current_block].data[block_pos];
|
|
const uint16_t pulse_len = (datum & (0x80>>current_bit)) == 0 ? PULSE_LEN_ZERO : PULSE_LEN_ONE;
|
|
if (pulse_pos >= pulse_len)
|
|
{
|
|
pulse_pos =0;//-= pulse_len;
|
|
pulse_level--;
|
|
if (pulse_level>=2)
|
|
{
|
|
pulse_level = 1;
|
|
//printf("%i\n",current_bit);
|
|
level[0]=level[1]=0;
|
|
current_bit++;
|
|
if (current_bit>=8)
|
|
{
|
|
current_bit = 0;
|
|
block_pos++;
|
|
if (block_pos>=blocks[current_block].length)
|
|
{
|
|
block_pos = 0;
|
|
current_section = PULSE_SYNC3;
|
|
printf("going to pulse_sync3..%i.\n", current_block);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/*if (current_section == PULSE_CHECKSUM)
|
|
{
|
|
const uint8_t datum = 1;//blocks[current_block].checksum;
|
|
const uint16_t pulse_len = (datum & (0x80>>current_bit)) == 0 ? PULSE_LEN_ZERO : PULSE_LEN_ONE;
|
|
if (pulse_pos >= pulse_len)
|
|
{
|
|
pulse_pos -= pulse_len;
|
|
pulse_level--;
|
|
if (pulse_level>=2)
|
|
{
|
|
pulse_level = 1;
|
|
current_bit++;
|
|
if (current_bit>=8)
|
|
{
|
|
current_bit = 0;
|
|
current_section = PULSE_SYNC3;
|
|
}
|
|
}
|
|
}
|
|
}*/
|
|
|
|
if (current_section == PULSE_SYNC3)
|
|
{
|
|
if (pulse_pos >= PULSE_LEN_SYNC3)
|
|
{
|
|
pulse_pos -= PULSE_LEN_SYNC3;
|
|
pulse_level = 0;
|
|
current_section = PULSE_WAIT;
|
|
printf("going to pulse_wait..%i (%i).\n", current_block, blocks[current_block].length);
|
|
}
|
|
|
|
}
|
|
|
|
if (current_section == PULSE_WAIT)
|
|
{
|
|
pulse_level = 0;
|
|
if (pulse_pos >= z80::getClock())
|
|
{
|
|
pulse_pos = 0;
|
|
current_section = PULSE_PILOT;
|
|
pulse_level = 1;
|
|
current_block++;
|
|
|
|
if (current_block>=blocks.size())
|
|
{
|
|
printf("end\n");
|
|
stop();
|
|
rewind();
|
|
if (options[ZXTAPE_OPTION_STOP_AT_END]) z80debug::stop();
|
|
}
|
|
else
|
|
{
|
|
//zxscreen::fullrefresh();
|
|
printf("going to pulse_pilot on block %i...\n", current_block);
|
|
}
|
|
}
|
|
}
|
|
|
|
zx_ula::set_ear(pulse_level);
|
|
}
|
|
|
|
const bool getplaying() { return playing; }
|
|
|
|
void report()
|
|
{
|
|
if (current_block >= blocks.size()) return;
|
|
const int percent = (float(block_pos)/float(blocks[current_block].length))*100;
|
|
printf("tape loading: %i%\n", percent);
|
|
}
|
|
|
|
uint16_t fastLoad(const uint8_t block_type, const uint16_t address, const uint16_t length)
|
|
{
|
|
block_pos=0;
|
|
current_bit=0;
|
|
current_section = PULSE_PILOT;
|
|
current_pulse = 0;
|
|
pulse_pos = 0;
|
|
pulse_level = 0;
|
|
|
|
if (blocks[current_block].data[0] != block_type ||
|
|
blocks[current_block].length != length+2) {
|
|
printf("ERROR: Tape data not consistent with expectation\n");
|
|
z80debug::stop();
|
|
}
|
|
for (int i=0;i<length;++i) {
|
|
mem::writeMem(address+i, blocks[current_block].data[i+1]);
|
|
}
|
|
current_block++;
|
|
return address + length;
|
|
}
|
|
|
|
const bool getOption(const int option)
|
|
{
|
|
return options[option];
|
|
}
|
|
|
|
void setOption(const int option, const bool value)
|
|
{
|
|
options[option] = value;
|
|
}
|
|
|
|
void toggleOption(const int option)
|
|
{
|
|
options[option] = !options[option];
|
|
}
|
|
}
|