blob: 3a18f75b6c7ef19eaff1dc898896ab1369ea086a [file] [log] [blame] [raw]
/*
YAPE - Yet Another Plus/4 Emulator
The program emulates the Commodore 264 family of 8 bit microcomputers
This program is free software, you are welcome to distribute it,
and/or modify it under certain conditions. For more information,
read 'Copying'.
(c) 2000, 2001, 2005, 2016-2018 Attila Grósz
*/
#include "tape.h"
#include "tedmem.h"
#include "cpu.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static unsigned int tapFrqs[] = {
C64PALFREQ, C64NTSCFREQ,
VICPALFREQ, VICNTSCFREQ,
C16PALFREQ, C16NTSCFREQ
};
#define NROFFRQS (sizeof(tapFrqs)/sizeof(tapFrqs[0]))
enum {
MTAP_IDSTRING = 0,
MTAP_VERSION = 12,
MTAP_PLATFORM,
MTAP_VIDEOFORMAT,
MTAP_RESERVED,
MTAP_DATASIZE
};
static unsigned char mtap_header_default[]={
'C','1','6','-','T','A','P','E','-','R','A','W',
0x02, // version -> 1 - "whole wave" 2 - "half wave"
0x02, // 0 - C64, 1 - VIC20, 2 - C16/+4
0x00, // Video standard ( 0= PAL, 1 =NTSC, 2 = NTSC2
0x00, // empty
// data length (4 byte) file size!!!!
0x00, 0x00, 0x00, 0x00
// data
};
const char tapeFormatStr[][32] = {
"MTAP1", "MTAP2", "PCM WAV 8-bit", "PCM WAV 16-bit", "Unknown"
};
TAP::TAP() : tapeFileSize(0), tapeBuffer(NULL), lastCycle(0), edge(0), buttonPressed(0), tapeSoFar(0)
{
buttonPressed = motorOn = false;
}
bool TAP::attachTape(const char *fname)
{
FILE *tapfile;
tapeFormat = TAPE_FORMAT_NONE;
if ((tapfile = fopen(fname,"rb"))) {
strcpy(tapefilename, fname);
// load TAP file into buffer
fseek(tapfile, 0L, SEEK_END);
tapeFileSize = ftell(tapfile);
fseek(tapfile, 0L, SEEK_SET);
// allocate and load file
tapeBuffer = new unsigned char[tapeFileSize];
if (fread(tapeBuffer, 1, tapeFileSize, tapfile) < 4)
return false;
tapeHeaderRead = tapeBuffer;
// determine the type of tape file attached
// MTAP?
if (!strncmp((const char *) tapeBuffer + 3,"-TAPE-RAW", 9)) {
tapeImageHeaderSize = tapeSoFar = sizeof(mtap_header_default); // offset to beginning of wave data
tapeFormat = (tapeHeaderRead[MTAP_VERSION] == 2) ? TAPE_FORMAT_MTAP2 : TAPE_FORMAT_MTAP1;
// some sanity checks for crappy TAPs
if (tapeHeaderRead[MTAP_PLATFORM] > 2)
tapeHeaderRead[MTAP_PLATFORM] = 0;
if (tapeHeaderRead[MTAP_VIDEOFORMAT] > 1)
tapeHeaderRead[MTAP_VIDEOFORMAT] = 0;
// set the MTAP frequency based on header
unsigned int index = tapeHeaderRead[MTAP_PLATFORM] * 2 + tapeHeaderRead[MTAP_VIDEOFORMAT];
tapeImageSampleRate = tapFrqs[index];
// trigger reading first pulse by starting high and no initial delay
edge = 0x10;
tapeDelay = 0;
// PCM WAV?
} else if (!memcmp(tapeBuffer, "RIFF", 4) && !memcmp(tapeBuffer + 8, "WAVEfmt ", 8)) {
tapeImageHeaderSize = tapeSoFar = sizeof(wav_header_t);
wav_header_t *wavh = (wav_header_t *)tapeHeaderRead;
tapeImageSampleRate = wavh->nSamplesPerSec;
// mono?
if (wavh->nChannels != 1)
return false;
tapeFormat = wavh->nBitsPerSample == 8 ? TAPE_FORMAT_PCM8 : TAPE_FORMAT_PCM16;
// if no match, assume it is a 44.1 kHz sample
} else {
tapeImageHeaderSize = tapeSoFar = 0;
tapeImageSampleRate = 44100;
tapeFormat = TAPE_FORMAT_PCM8;
}
motorOn = buttonPressed = false;
// close the file, it's in the memory now...
fclose(tapfile);
fprintf(stderr, "Tape attached : %s\n", tapefilename);
fprintf(stderr, "Tape format : %s\n", tapeFormatStr[(unsigned int)tapeFormat]);
fprintf(stderr, "Tape data size : %3.1f kBytes\n", double(tapeFileSize) / 1024.0);
fprintf(stderr, "Tape sample rate : %u Hz\n", tapeImageSampleRate);
return true;
}
return false;
}
bool TAP::createTape(const char *fname)
{
FILE *tapfile;
unsigned int filesize = 0;
if (strlen(fname) < 5)
return false;
// initialise TAP image
if ((tapfile = fopen(tapefilename,"wb"))) {
strcpy(tapefilename, fname);
// write the header
fwrite(mtap_header_default, sizeof(mtap_header_default), 1, tapfile);
fwrite(&filesize, 4, 1, tapfile);
return true;
}
else
return false;
}
bool TAP::detachTape()
{
//if (tapfile != NULL && TapeSoFar > 0) {
// fseek(tapfile, 0x10L, SEEK_SET);
// fwrite(&TapeSoFar, sizeof(TapeSoFar), 1, tapfile);
// fseek(tapfile, 0L, SEEK_END);
// fclose(tapfile);
// tapfile = NULL;
//}
if (tapeBuffer != NULL) { // just to be sure....
delete [] tapeBuffer;
tapeBuffer = NULL;
}
tapeSoFar = 0;
return false;
}
void TAP::rewind()
{
tapeSoFar = tapeImageHeaderSize;
if (tapeFormat <= TAPE_FORMAT_MTAP2) {
// start high since XOR is triggered
edge = 0x10;
tapeDelay = 0;
} else
edge = 0x00;
}
void TAP::changewave(bool wholewave)
{
mtap_header_default[MTAP_VERSION] = wholewave ? 1 : 2;
}
inline void TAP::readMtapData(unsigned int elapsed)
{
while (elapsed--) {
if (!tapeDelay--) {
//fprintf( stderr, "TAP unit timeout in cycle: %u\n", elapsed);
if (edge == 0x10) {
convTAPUnitsToCycles();
fallingEdge = true;
} else {
tapeDelay = origTapeDelay;
}
edge ^= 0x10;
}
}
}
inline void TAP::readWavData(unsigned int elapsed)
{
const unsigned int fastClockFreq = mem->getRealSlowClock() << 1;
static int prevSample = 0;
while (elapsed--) {
if (tapeSoFar >= tapeFileSize)
return;
if ((tapeDelay += tapeImageSampleRate) >= fastClockFreq) {
int sample = tapeBuffer[tapeSoFar++];
if (tapeFormat == TAPE_FORMAT_PCM16) {
sample += tapeBuffer[tapeSoFar++] << 8;
int change = short(sample) - prevSample;
if (sample > 0 && change > 0) {
edge = 0x10;
} else if (sample <= 0 && change < 0) {
edge = 0x00;
fallingEdge = true;
}
} else {
int change = sample - prevSample;
if (sample > 0x80 && change > 0) {
edge = 0x10;
} else if (sample <= 0x7F && change < 0) {
edge = 0x00;
fallingEdge = true;
}
}
prevSample = sample;
tapeDelay -= fastClockFreq;
}
}
}
unsigned char TAP::readCSTIn(ClockCycle cycle)
{
int elapsed = int(cycle - lastCycle);
if (motorOn && tapeBuffer) {
switch (tapeFormat) {
case TAPE_FORMAT_MTAP1:
case TAPE_FORMAT_MTAP2:
readMtapData(elapsed);
break;
case TAPE_FORMAT_PCM8:
readWavData(elapsed);
break;
case TAPE_FORMAT_NONE:
default:
break;
}
lastCycle = cycle;
}
return edge;
}
inline unsigned int TAP::readNextTapDelay()
{
unsigned int delay = tapeBuffer[tapeSoFar++];
/* byte $00 is a pilot byte */
if (!delay) {
delay = tapeBuffer[tapeSoFar++];
delay += tapeBuffer[tapeSoFar++] << 8;
delay += tapeBuffer[tapeSoFar++] << 16;
/*fprintf( stderr, "Pilot byte %i.\n", delay);
fprintf( stderr, "Pilot offset: %i.\n", TapeSoFar);*/
} else {
delay <<= 3;
}
unsigned int tapClockFreq = mem->getRealSlowClock() >> 4;
// machine clock frequency different from MTAP one? then adjust...
if (tapeImageSampleRate != tapClockFreq) {
const double frqMult = double(tapClockFreq) / double(tapeImageSampleRate);
delay = (unsigned int)(double(delay) * frqMult + 0.5);
}
if (tapeSoFar >= tapeFileSize) {
motorOn = buttonPressed = false;
}
return delay << 1;
}
void TAP::convTAPUnitsToCycles()
{
if (tapeFormat == TAPE_FORMAT_MTAP1) {
origTapeDelay = readNextTapDelay();
tapeDelay = origTapeDelay >> 1;
origTapeDelay -= tapeDelay;
} else {
tapeDelay = readNextTapDelay();
origTapeDelay = readNextTapDelay();
}
//fprintf(stderr, "New TAP delay value: %u\n", tapeDelay);
}
void TAP::pressTapeButton(ClockCycle cycle, unsigned int pressed)
{
if (pressed) {
buttonPressed = 1;
} else {
buttonPressed = 0;
motorOn = false;
}
}
void TAP::setTapeMotor(ClockCycle cycle, unsigned int on)
{
if (!on && motorOn)
readCSTIn(cycle);
else if (on && !motorOn)
lastCycle = cycle;
motorOn = (on != 0);
//fprintf( stderr, "Motor state: %i, in cycle %u\n", motorOn, cycle);
}