2016-08-20 13:29:27 -04:00
|
|
|
#pragma once
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include "BaseMapper.h"
|
2019-11-22 23:36:56 -05:00
|
|
|
#include "MemoryManager.h"
|
2019-12-31 20:23:26 -05:00
|
|
|
#include "Console.h"
|
2020-01-02 09:27:25 -05:00
|
|
|
#include "CPU.h"
|
2019-12-31 20:23:26 -05:00
|
|
|
#include "../Utilities/WavReader.h"
|
2019-11-22 23:36:56 -05:00
|
|
|
#include "../Utilities/FolderUtilities.h"
|
2019-12-31 20:23:26 -05:00
|
|
|
#include "../Utilities/StringUtilities.h"
|
2019-11-24 15:03:04 -05:00
|
|
|
#include "../Utilities/HexUtilities.h"
|
2016-08-20 13:29:27 -04:00
|
|
|
|
|
|
|
class StudyBox : public BaseMapper
|
|
|
|
{
|
|
|
|
private:
|
2019-12-31 20:23:26 -05:00
|
|
|
shared_ptr<WavReader> _wavReader;
|
2020-01-01 22:21:15 -05:00
|
|
|
uint32_t _audioSampleRate;
|
2019-12-31 20:23:26 -05:00
|
|
|
|
2019-11-22 23:36:56 -05:00
|
|
|
bool _readyForBit = false;
|
|
|
|
uint16_t _processBitDelay = 0;
|
2016-10-30 10:37:15 -04:00
|
|
|
uint8_t _reg4202 = 0;
|
2016-08-20 13:29:27 -04:00
|
|
|
|
2019-11-22 23:36:56 -05:00
|
|
|
uint8_t _commandCounter = 0;
|
|
|
|
uint8_t _command = 0;
|
2016-08-20 13:29:27 -04:00
|
|
|
|
2019-11-22 23:36:56 -05:00
|
|
|
uint8_t _currentPage = 0;
|
|
|
|
int16_t _seekPage = 0;
|
|
|
|
uint32_t _seekPageDelay = 0;
|
2016-08-20 13:29:27 -04:00
|
|
|
|
2019-11-22 23:36:56 -05:00
|
|
|
bool _enableDecoder = false;
|
|
|
|
|
2019-12-31 20:23:26 -05:00
|
|
|
bool _audioEnabled = false;
|
2019-11-24 15:03:04 -05:00
|
|
|
bool _motorDisabled = false;
|
|
|
|
uint16_t _byteReadDelay = 0;
|
2019-11-22 23:36:56 -05:00
|
|
|
bool _irqEnabled = false;
|
|
|
|
|
|
|
|
bool _pageFound = false;
|
2019-12-31 20:23:26 -05:00
|
|
|
StudyBoxData _tapeData;
|
|
|
|
int32_t _pageIndex = 0;
|
2019-11-24 15:03:04 -05:00
|
|
|
int32_t _pagePosition = -1;
|
|
|
|
|
|
|
|
uint32_t _inDataDelay = 0;
|
2019-11-22 23:36:56 -05:00
|
|
|
bool _inDataRegion = false;
|
|
|
|
|
|
|
|
protected:
|
|
|
|
uint16_t RegisterStartAddress() override { return 0x4200; }
|
|
|
|
uint16_t RegisterEndAddress() override { return 0x4203; }
|
|
|
|
bool AllowRegisterRead() override { return true; }
|
|
|
|
|
|
|
|
uint16_t GetPRGPageSize() override { return 0x4000; }
|
|
|
|
uint16_t GetCHRPageSize() override { return 0x2000; }
|
|
|
|
|
|
|
|
uint32_t GetWorkRamSize() override { return 0x10000; }
|
|
|
|
uint32_t GetWorkRamPageSize() override { return 0x1000; }
|
2016-08-20 13:29:27 -04:00
|
|
|
|
2016-12-17 23:14:47 -05:00
|
|
|
void InitMapper() override
|
2016-08-20 13:29:27 -04:00
|
|
|
{
|
|
|
|
SelectPRGPage(1, 0);
|
|
|
|
SelectCHRPage(0, 0);
|
2019-11-23 21:23:35 -05:00
|
|
|
|
2020-01-01 22:21:15 -05:00
|
|
|
//First bank (on the 2nd RAM chip, so bank #8 in the code) is mapped to 4000-4FFF, but the first 1kb is not accessible
|
2019-11-23 21:23:35 -05:00
|
|
|
SetCpuMemoryMapping(0x4000, 0x4FFF, 8, PrgMemoryType::WorkRam);
|
|
|
|
RemoveCpuMemoryMapping(0x4000, 0x43FF);
|
|
|
|
|
|
|
|
SetMirroringType(MirroringType::FourScreens);
|
2016-08-20 13:29:27 -04:00
|
|
|
}
|
|
|
|
|
2019-12-31 20:23:26 -05:00
|
|
|
void InitMapper(RomData& romData) override
|
|
|
|
{
|
|
|
|
_tapeData = romData.StudyBox;
|
|
|
|
_wavReader = WavReader::Create(_tapeData.AudioFile.data(), (uint32_t)_tapeData.AudioFile.size());
|
|
|
|
if(!_wavReader) {
|
2020-01-01 22:21:15 -05:00
|
|
|
_audioSampleRate = 44100;
|
2019-12-31 20:23:26 -05:00
|
|
|
MessageManager::Log("[Study Box] Unsupported audio file format.");
|
2020-01-01 22:21:15 -05:00
|
|
|
} else {
|
|
|
|
_audioSampleRate = _wavReader->GetSampleRate();
|
2019-12-31 20:23:26 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-12-17 23:14:47 -05:00
|
|
|
void StreamState(bool saving) override
|
2016-08-20 13:29:27 -04:00
|
|
|
{
|
|
|
|
BaseMapper::StreamState(saving);
|
2019-12-31 20:23:26 -05:00
|
|
|
int32_t audioPosition = _wavReader ? _wavReader->GetPosition() : -1;
|
|
|
|
Stream(
|
|
|
|
_readyForBit, _processBitDelay, _reg4202, _commandCounter, _command, _currentPage, _seekPage, _seekPageDelay, _enableDecoder,
|
|
|
|
_audioEnabled, _motorDisabled, _byteReadDelay, _irqEnabled, _pageFound, _pageIndex, _pagePosition, _inDataDelay, _inDataRegion, audioPosition
|
|
|
|
);
|
|
|
|
|
|
|
|
if(!saving && audioPosition >= 0 && _wavReader) {
|
|
|
|
_wavReader->SetSampleRate(_console->GetSettings()->GetSampleRate());
|
|
|
|
_wavReader->Play(audioPosition);
|
|
|
|
}
|
2016-08-20 13:29:27 -04:00
|
|
|
}
|
|
|
|
|
2020-01-01 22:21:15 -05:00
|
|
|
void ReadLeadInTrack()
|
|
|
|
{
|
|
|
|
//Wait for the tape to read through the lead-in before the actual data
|
|
|
|
_inDataDelay = (uint64_t)(_tapeData.Pages[_pageIndex].AudioOffset - _tapeData.Pages[_pageIndex].LeadInOffset) * _console->GetCpu()->GetClockRate(_console->GetModel()) / _audioSampleRate;
|
|
|
|
_pagePosition = -1;
|
|
|
|
_byteReadDelay = 0;
|
|
|
|
_motorDisabled = false;
|
|
|
|
_pageFound = true;
|
|
|
|
}
|
|
|
|
|
2019-11-22 23:36:56 -05:00
|
|
|
void ProcessCpuClock() override
|
2016-08-20 13:29:27 -04:00
|
|
|
{
|
2019-11-22 23:36:56 -05:00
|
|
|
if(_processBitDelay > 0) {
|
|
|
|
_processBitDelay--;
|
|
|
|
if(_processBitDelay == 0) {
|
|
|
|
_readyForBit = true;
|
|
|
|
}
|
2016-08-20 13:29:27 -04:00
|
|
|
}
|
|
|
|
|
2019-12-31 20:23:26 -05:00
|
|
|
if(_motorDisabled) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if(_seekPage != _currentPage) {
|
2019-11-22 23:36:56 -05:00
|
|
|
_seekPageDelay--;
|
|
|
|
if(_seekPageDelay == 0) {
|
2019-11-24 15:03:04 -05:00
|
|
|
_seekPageDelay = 3000000;
|
2019-12-31 20:23:26 -05:00
|
|
|
if(_seekPage > _currentPage) {
|
2019-11-22 23:36:56 -05:00
|
|
|
_currentPage++;
|
|
|
|
} else {
|
|
|
|
_currentPage--;
|
|
|
|
}
|
2019-11-24 15:03:04 -05:00
|
|
|
|
2019-12-31 20:23:26 -05:00
|
|
|
_pageIndex = 0;
|
|
|
|
for(size_t i = 0; i < _tapeData.Pages.size(); i++) {
|
|
|
|
if(_tapeData.Pages[i].Data[5] == _currentPage - 1) {
|
|
|
|
//Find the first page that matches the requested page number
|
|
|
|
_pageIndex = (int32_t)i;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-01 22:21:15 -05:00
|
|
|
ReadLeadInTrack();
|
2019-11-22 23:36:56 -05:00
|
|
|
}
|
2019-12-31 20:23:26 -05:00
|
|
|
} else if(_inDataDelay > 0) {
|
2019-11-24 15:03:04 -05:00
|
|
|
_inDataRegion = true;
|
|
|
|
_inDataDelay--;
|
|
|
|
if(_inDataDelay == 0) {
|
|
|
|
_byteReadDelay = 7820;
|
2019-12-31 20:23:26 -05:00
|
|
|
if(_wavReader) {
|
|
|
|
_wavReader->SetSampleRate(_console->GetSettings()->GetSampleRate());
|
|
|
|
_wavReader->Play(_tapeData.Pages[_pageIndex].AudioOffset);
|
|
|
|
}
|
2019-11-22 23:36:56 -05:00
|
|
|
_console->GetCpu()->SetIrqSource(IRQSource::External);
|
|
|
|
}
|
2019-12-31 20:23:26 -05:00
|
|
|
} else if(_byteReadDelay > 0) {
|
2019-11-24 15:03:04 -05:00
|
|
|
_byteReadDelay--;
|
|
|
|
if(_byteReadDelay == 0) {
|
|
|
|
_byteReadDelay = 3355;
|
|
|
|
_pagePosition++;
|
|
|
|
|
2019-12-31 20:23:26 -05:00
|
|
|
if(_pagePosition >= (int32_t)_tapeData.Pages[_pageIndex].Data.size()) {
|
2019-11-24 15:03:04 -05:00
|
|
|
_pageFound = false;
|
|
|
|
_inDataRegion = false;
|
2019-12-31 20:23:26 -05:00
|
|
|
_motorDisabled = true;
|
2019-11-24 15:03:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if(_irqEnabled) {
|
|
|
|
_console->GetCpu()->SetIrqSource(IRQSource::External);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2019-11-22 23:36:56 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t ReadRegister(uint16_t addr) override
|
2016-10-30 10:37:15 -04:00
|
|
|
{
|
2019-11-22 23:36:56 -05:00
|
|
|
switch(addr) {
|
|
|
|
case 0x4200: {
|
|
|
|
if(!_enableDecoder) {
|
|
|
|
MessageManager::Log("Error - read 4200 without decoder being enabled");
|
|
|
|
}
|
2019-11-24 15:03:04 -05:00
|
|
|
|
2019-11-22 23:36:56 -05:00
|
|
|
_console->GetCpu()->ClearIrqSource(IRQSource::External);
|
2019-12-31 20:23:26 -05:00
|
|
|
if(_pagePosition >= 0 && _pagePosition < (int32_t)_tapeData.Pages[_pageIndex].Data.size()) {
|
|
|
|
//MessageManager::Log("Read: " + HexUtilities::ToHex(_tapeData.Pages[_pageIndex].Data[_pagePosition]));
|
|
|
|
return _tapeData.Pages[_pageIndex].Data[_pagePosition];
|
2019-11-22 23:36:56 -05:00
|
|
|
}
|
2019-12-31 20:23:26 -05:00
|
|
|
|
|
|
|
//After command $86, games expect to read 1 $AA byte before the $C5 header
|
|
|
|
return 0xAA;
|
2016-10-30 10:37:15 -04:00
|
|
|
}
|
2019-11-22 23:36:56 -05:00
|
|
|
|
|
|
|
case 0x4201: {
|
|
|
|
/* Tape read status ?
|
|
|
|
$80 - something to do with $4202.0 ? decoder disabled ? | decoder data ready ?
|
|
|
|
$40 - tape data clock synched ? current tape data bit ? | current tape flux polarity ? tape motor running ? | seek complete ?
|
|
|
|
$20 - set when in data region during seek ? possibly set when in data region generally ? or set normally and cleared when in a data region ?
|
|
|
|
$10 - ? ? ? */
|
|
|
|
uint8_t value = (
|
|
|
|
//0x10 |
|
|
|
|
(_inDataRegion ? 0x20 : 0) |
|
|
|
|
(_pageFound ? 0x40 : 0) |
|
|
|
|
(_enableDecoder ? 0x80 : 0)
|
|
|
|
);
|
|
|
|
|
2019-11-24 15:03:04 -05:00
|
|
|
_pageFound = false;
|
|
|
|
|
2019-11-22 23:36:56 -05:00
|
|
|
return value;
|
|
|
|
}
|
|
|
|
|
|
|
|
case 0x4202:
|
|
|
|
//Tape drive status?
|
|
|
|
//$40 - shift register ready for next bit?
|
|
|
|
//$08 - power supply not connected
|
|
|
|
return (
|
|
|
|
//(_powerDisconnected ? 0x08 : 0) |
|
|
|
|
(_readyForBit ? 0x40 : 0)
|
|
|
|
);
|
|
|
|
|
|
|
|
case 0x4203:
|
|
|
|
//unused?
|
|
|
|
return 0x00;
|
2016-10-30 10:37:15 -04:00
|
|
|
}
|
2019-11-22 23:36:56 -05:00
|
|
|
|
|
|
|
return 0;
|
2016-10-30 10:37:15 -04:00
|
|
|
}
|
|
|
|
|
2016-12-17 23:14:47 -05:00
|
|
|
void WriteRegister(uint16_t addr, uint8_t value) override
|
2016-08-20 13:29:27 -04:00
|
|
|
{
|
2019-11-22 23:36:56 -05:00
|
|
|
switch(addr) {
|
|
|
|
case 0x4200:
|
2019-11-23 21:23:35 -05:00
|
|
|
SetCpuMemoryMapping(0x6000, 0x6FFF, (value & 0xC0) >> 5, PrgMemoryType::WorkRam);
|
|
|
|
SetCpuMemoryMapping(0x7000, 0x7FFF, ((value & 0xC0) >> 5) + 1, PrgMemoryType::WorkRam);
|
2019-11-22 23:36:56 -05:00
|
|
|
SetCpuMemoryMapping(0x5000, 0x5FFF, (value & 0x07) + 8, PrgMemoryType::WorkRam);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4201:
|
|
|
|
//PRG Select
|
|
|
|
SelectPRGPage(0, value);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4202:
|
|
|
|
/*Tape drive control
|
|
|
|
$80 - output data bit
|
|
|
|
$40 - ???
|
|
|
|
$20 - pulse low to reset drive controller?
|
|
|
|
$10 - pulse low to clock data bit
|
|
|
|
$08 - ???
|
|
|
|
$04 - ??? maybe tape audio enable?
|
|
|
|
$02 - irq enable?
|
|
|
|
$01 - data decoding enable?
|
|
|
|
*/
|
|
|
|
if((_reg4202 & 0x10) && !(value & 0x10)) {
|
|
|
|
if(!_readyForBit) {
|
|
|
|
MessageManager::Log("Error - write without being ready");
|
|
|
|
}
|
|
|
|
//Clock command bit
|
|
|
|
_command <<= 1;
|
|
|
|
_command |= (value & 0x80) >> 7;
|
|
|
|
_commandCounter++;
|
|
|
|
|
|
|
|
if(_commandCounter == 8) {
|
|
|
|
_commandCounter = 0;
|
2019-12-31 20:23:26 -05:00
|
|
|
//MessageManager::Log("Command sent: " + std::to_string(_command));
|
2019-11-22 23:36:56 -05:00
|
|
|
|
|
|
|
if(_command >= 1 && _command < 0x40) {
|
2020-01-01 22:21:15 -05:00
|
|
|
//Move forward X pages
|
2019-12-31 20:23:26 -05:00
|
|
|
_seekPage = _command + _currentPage;
|
2019-11-24 15:03:04 -05:00
|
|
|
_seekPageDelay = 3000000;
|
|
|
|
_motorDisabled = false;
|
2019-11-22 23:36:56 -05:00
|
|
|
} else if(_command > 0x40 && _command < 0x80) {
|
2020-01-01 22:21:15 -05:00
|
|
|
//Move back X pages
|
2019-12-31 20:23:26 -05:00
|
|
|
_seekPage = -(_command - 0x40) + _currentPage;
|
2019-11-24 15:03:04 -05:00
|
|
|
_seekPageDelay = 3000000;
|
|
|
|
_motorDisabled = false;
|
|
|
|
} else if(_command == 0) {
|
2020-01-01 22:21:15 -05:00
|
|
|
//Move back to the start of this page (based on the internal page ID, not the page "index" in the array? - to validate)
|
2019-12-31 20:23:26 -05:00
|
|
|
_seekPage = _currentPage;
|
|
|
|
_currentPage = _currentPage - 1;
|
2019-11-24 15:03:04 -05:00
|
|
|
_seekPageDelay = 3000000;
|
|
|
|
_motorDisabled = false;
|
|
|
|
} else if(_command == 0x86) {
|
2020-01-01 22:21:15 -05:00
|
|
|
//Reenable motor (and continue to the next page)
|
|
|
|
if(_pageIndex < (int32_t)_tapeData.Pages.size() - 1) {
|
2019-12-31 20:23:26 -05:00
|
|
|
_pageIndex++;
|
|
|
|
} else {
|
2020-01-01 22:21:15 -05:00
|
|
|
//Pretend we go back to the start of the tape (inaccurate)
|
|
|
|
_pageIndex = 0;
|
2019-11-24 15:03:04 -05:00
|
|
|
}
|
2020-01-01 22:21:15 -05:00
|
|
|
ReadLeadInTrack();
|
2019-12-31 20:23:26 -05:00
|
|
|
} else {
|
|
|
|
MessageManager::Log("Unknown command sent: " + std::to_string(_command));
|
2019-11-22 23:36:56 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if(value & 0x10) {
|
|
|
|
_readyForBit = false;
|
|
|
|
_processBitDelay = 100;
|
|
|
|
}
|
|
|
|
|
2019-12-31 20:23:26 -05:00
|
|
|
/*if((_reg4202 & 0x6E) != (value & 0x6E)) {
|
2019-11-24 15:03:04 -05:00
|
|
|
MessageManager::Log("Reg 4202 value changed: " + HexUtilities::ToHex(_reg4202) + " -> " + HexUtilities::ToHex(value));
|
2019-12-31 20:23:26 -05:00
|
|
|
}*/
|
2019-11-24 15:03:04 -05:00
|
|
|
|
2019-11-22 23:36:56 -05:00
|
|
|
if((_reg4202 & 0x20) && !(value & 0x20)) {
|
|
|
|
//Reset drive
|
|
|
|
_command = 0;
|
|
|
|
_commandCounter = 0;
|
|
|
|
_readyForBit = true;
|
|
|
|
}
|
|
|
|
|
2019-11-24 15:03:04 -05:00
|
|
|
if((value & 0x04) != (_reg4202 & 0x04)) {
|
2019-12-31 20:23:26 -05:00
|
|
|
_audioEnabled = (value & 0x04) == 0;
|
|
|
|
//MessageManager::Log(_audioEnabled ? "Audio enabled" : "Audio disabled");
|
2019-11-24 15:03:04 -05:00
|
|
|
}
|
|
|
|
|
|
|
|
if((value & 0x02) != (_reg4202 & 0x02)) {
|
2019-12-31 20:23:26 -05:00
|
|
|
//MessageManager::Log((value & 0x02) ? "IRQ enabled" : "IRQ disabled");
|
2019-11-24 15:03:04 -05:00
|
|
|
}
|
2019-11-22 23:36:56 -05:00
|
|
|
|
2019-11-24 15:03:04 -05:00
|
|
|
/*if((value & 0x01) != (_reg4202 & 0x01)) {
|
|
|
|
MessageManager::Log((value & 0x01) ? "Decoder enabled" : "Decoder disabled");
|
|
|
|
}*/
|
|
|
|
|
|
|
|
_reg4202 = value;
|
2019-11-22 23:36:56 -05:00
|
|
|
_enableDecoder = value & 0x01;
|
|
|
|
_irqEnabled = value & 0x02;
|
|
|
|
_console->GetCpu()->ClearIrqSource(IRQSource::External);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case 0x4203:
|
|
|
|
//Unused?
|
|
|
|
break;
|
2016-08-20 13:29:27 -04:00
|
|
|
}
|
|
|
|
}
|
2019-12-31 20:23:26 -05:00
|
|
|
|
|
|
|
public:
|
|
|
|
void ApplySamples(int16_t* buffer, size_t sampleCount, double volume) override
|
|
|
|
{
|
|
|
|
if(!_motorDisabled && _wavReader) {
|
|
|
|
_wavReader->ApplySamples(buffer, sampleCount, _audioEnabled ? volume : 0);
|
2020-01-01 18:47:55 -05:00
|
|
|
_wavReader->SetSampleRate(_console->GetSettings()->GetSampleRate());
|
2019-12-31 20:23:26 -05:00
|
|
|
}
|
|
|
|
}
|
2016-08-20 13:29:27 -04:00
|
|
|
};
|