Files
VirtualC64-Core/C64/Datasette.cpp
T
2015-11-18 00:34:41 -06:00

307 lines
7.3 KiB
C++
Executable File

/*
* Written 2015 by Dirk W. Hoffmann
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
#include "C64.h"
Datasette::Datasette()
{
name = "Datasette";
debug(2, "Creating virtual datasette at address %p\n", this);
// Register snapshot items
SnapshotItem items[] = {
// Tape properties (will survive reset)
{ &size, sizeof(size), KEEP_ON_RESET },
{ &type, sizeof(type), KEEP_ON_RESET },
{ &durationInCycles, sizeof(durationInCycles), KEEP_ON_RESET },
// Internal state (will be cleared on reset)
{ &head, sizeof(head), CLEAR_ON_RESET },
{ &headInCycles, sizeof(headInCycles), CLEAR_ON_RESET },
{ &headInSeconds, sizeof(headInSeconds), CLEAR_ON_RESET },
{ &nextRisingEdge, sizeof(nextRisingEdge), CLEAR_ON_RESET },
{ &nextFallingEdge, sizeof(nextFallingEdge), CLEAR_ON_RESET },
{ &playKey, sizeof(playKey), CLEAR_ON_RESET },
{ &motor, sizeof(motor), CLEAR_ON_RESET },
{ NULL, 0, 0 }};
registerSnapshotItems(items, sizeof(items));
// Initialize all values that are not initialized in reset()
data = NULL;
size = 0;
type = 0;
durationInCycles = 0;
}
Datasette::~Datasette()
{
debug(2, "Releasing Datasette...\n");
if (data)
delete data;
}
void
Datasette::reset()
{
VirtualComponent::reset();
rewind();
}
void
Datasette::ping()
{
debug(2, "Pinging Datasette...\n");
c64->putMessage(MSG_VC1530_TAPE, hasTape() ? 1 : 0);
// c64->putMessage(MSG_VC1530_MOTOR, motor ? 1 : 0);
// c64->putMessage(MSG_VC1530_PLAY, playKey ? 1 : 0);
c64->putMessage(MSG_VC1530_PROGRESS, headInSeconds);
}
uint32_t
Datasette::stateSize()
{
return VirtualComponent::stateSize() + size;
}
void
Datasette::loadFromBuffer(uint8_t **buffer)
{
uint8_t *old = *buffer;
VirtualComponent::loadFromBuffer(buffer);
if (size) {
if (data == NULL)
data = (uint8_t *)malloc(size);
readBlock(buffer, (uint8_t *)data, size);
}
if (*buffer - old != stateSize())
assert(0);
}
void
Datasette::saveToBuffer(uint8_t **buffer)
{
uint8_t *old = *buffer;
VirtualComponent::saveToBuffer(buffer);
if (size) {
assert(data != NULL);
writeBlock(buffer, (uint8_t *)data, size);
}
if (*buffer - old != stateSize())
assert(0);
}
void
Datasette::dumpState()
{
#if 0
msg("Datasette\n");
msg("---------\n\n");
msg(" Bit ready timer : %d\n", bitReadyTimer);
msg(" Head position : Track %d, Bit offset %d\n", halftrack, bitoffset);
msg(" SYNC : %d\n", sync);
msg(" Read mode : %s\n", readMode() ? "YES" : "NO");
msg("\n");
#endif
}
void
Datasette::setHeadInCycles(uint64_t value)
{
printf("Fast forwarding to cycle %lld (duration %lld)\n", value, durationInCycles);
rewind();
while (headInCycles <= value && head < size)
advanceHead(true);
printf("Head is %u (max %d)\n", head, size);
}
void
Datasette::insertTape(TAPArchive *a)
{
size = a->getSize();
type = a->TAPversion();
debug(2, "Inserting tape (size = %d, type = %d)...\n", size, type);
// Copy data
data = (uint8_t *)malloc(size);
memcpy(data, a->getData(), size);
// Determine tape length (by fast forwarding)
rewind();
while (head < size)
advanceHead(true /* Don't send tape progress messages */);
durationInCycles = headInCycles;
rewind();
c64->putMessage(MSG_VC1530_TAPE, 1);
}
void
Datasette::ejectTape()
{
debug(2, "Ejecting tape\n");
if (!hasTape())
return;
pressStop();
assert(data != NULL);
free(data);
data = NULL;
size = 0;
type = 0;
durationInCycles = 0;
head = -1;
c64->putMessage(MSG_VC1530_TAPE, 0);
}
void
Datasette::advanceHead(bool silent)
{
// Return if end of tape is already reached
if (head == size)
return;
// Update head and headInCycles
int length, skip;
length = pulseLength(&skip);
head += skip;
headInCycles += length;
// Send message if the tapeCounter (in seconds) changes
uint32_t newHeadInSeconds = headInCycles / PAL_CYCLES_PER_SECOND;
if (newHeadInSeconds != headInSeconds && !silent)
c64->putMessage(MSG_VC1530_PROGRESS, newHeadInSeconds);
// Update headInSeconds
headInSeconds = newHeadInSeconds;
}
int
Datasette::pulseLength(int *skip)
{
assert(head < size);
if (data[head] != 0) {
// Pulse lengths between 1 * 8 and 255 * 8
if (skip) *skip = 1;
return 8 * data[head];
}
if (type == 0) {
// Pulse lengths greater than 8 * 255 (TAP V0 files)
if (skip) *skip = 1;
return 8 * 256;
} else {
// Pulse lengths greater than 8 * 255 (TAP V1 files)
if (skip) *skip = 4;
return LO_LO_HI_HI(data[head+1], data[head+2], data[head+3], 0);
}
}
void
Datasette::pressPlay()
{
if (!hasTape())
return;
debug("Datasette::pressPlay\n");
// nextFallingEdge = 0.5 * PAL_CYCLES_PER_SECOND; /* kick off in 0.5 seconds */
// nextRisingEdge = -1;
// Schedule first pulse
uint64_t length = pulseLength();
nextRisingEdge = length / 2;
nextFallingEdge = length;
playKey = true;
// c64->putMessage(MSG_VC1530_PLAY, true);
}
void
Datasette::pressStop()
{
debug("Datasette::pressStop\n");
setMotor(false);
playKey = false;
// c64->putMessage(MSG_VC1530_PLAY, false);
}
void
Datasette::setMotor(bool value)
{
if (motor == value)
return;
motor = value;
// c64->putMessage(MSG_VC1530_MOTOR, motor);
}
void
Datasette::_execute()
{
if (!hasTape() || !playKey || !motor)
return;
nextRisingEdge--;
nextFallingEdge--;
if (nextRisingEdge == 0) {
_executeRising();
return;
}
if (nextFallingEdge == 0 && head < size) {
_executeFalling();
return;
}
if (head >= size) {
pressStop();
}
}
void
Datasette::_executeRising()
{
c64->cia1->triggerRisingEdgeOnFlagPin();
}
void
Datasette::_executeFalling()
{
c64->cia1->triggerFallingEdgeOnFlagPin();
// Schedule next pulse
advanceHead();
uint64_t length = pulseLength();
nextRisingEdge = length / 2;
nextFallingEdge = length;
}