307 lines
7.3 KiB
C++
Executable File
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;
|
|
} |