mirror of
https://github.com/TomHarte/CLK.git
synced 2024-12-21 23:29:25 +00:00
Attempts to implement TSX support for the MSX.
This commit is contained in:
parent
51e82c10c5
commit
db6d9b59d0
@ -17,6 +17,8 @@
|
||||
#include "../../Components/8255/i8255.hpp"
|
||||
#include "../../Components/AY38910/AY38910.hpp"
|
||||
|
||||
#include "../../Storage/Tape/Tape.hpp"
|
||||
|
||||
#include "../CRTMachine.hpp"
|
||||
#include "../ConfigurationTarget.hpp"
|
||||
#include "../KeyboardMachine.hpp"
|
||||
@ -61,15 +63,32 @@ class AudioToggle: public Outputs::Speaker::SampleSource {
|
||||
Concurrency::DeferringAsyncTaskQueue &audio_queue_;
|
||||
};
|
||||
|
||||
struct AYPortHandler: public GI::AY38910::PortHandler {
|
||||
void set_port_output(bool port_b, uint8_t value) {
|
||||
// printf("AY port %c output: %02x\n", port_b ? 'b' : 'a', value);
|
||||
}
|
||||
class AYPortHandler: public GI::AY38910::PortHandler {
|
||||
public:
|
||||
AYPortHandler(Storage::Tape::BinaryTapePlayer &tape_player) : tape_player_(tape_player) {}
|
||||
|
||||
uint8_t get_port_input(bool port_b) {
|
||||
// printf("AY port %c input\n", port_b ? 'b' : 'a');
|
||||
return 0xff;
|
||||
}
|
||||
void set_port_output(bool port_b, uint8_t value) {
|
||||
if(port_b) {
|
||||
// Bits 0–3: touchpad handshaking (?)
|
||||
// Bit 4—5: monostable timer pulses
|
||||
// Bit 6: joystick select
|
||||
// Bit 7: code LED, if any
|
||||
}
|
||||
}
|
||||
|
||||
uint8_t get_port_input(bool port_b) {
|
||||
if(!port_b) {
|
||||
// Bits 0–5: Joystick (up, down, left, right, A, B)
|
||||
// Bit 6: keyboard switch (not universal)
|
||||
|
||||
// Bit 7: tape input
|
||||
return 0x7f | (tape_player_.get_input() ? 0x80 : 0x00);
|
||||
}
|
||||
return 0xff;
|
||||
}
|
||||
|
||||
private:
|
||||
Storage::Tape::BinaryTapePlayer &tape_player_;
|
||||
};
|
||||
|
||||
class ConcreteMachine:
|
||||
@ -86,7 +105,9 @@ class ConcreteMachine:
|
||||
audio_toggle_(audio_queue_),
|
||||
mixer_(ay_, audio_toggle_),
|
||||
speaker_(mixer_),
|
||||
i8255_port_handler_(*this, audio_toggle_) {
|
||||
tape_player_(3579545 * 2),
|
||||
i8255_port_handler_(*this, audio_toggle_, tape_player_),
|
||||
ay_port_handler_(tape_player_) {
|
||||
set_clock_rate(3579545);
|
||||
std::memset(unpopulated_, 0xff, sizeof(unpopulated_));
|
||||
clear_all_keys();
|
||||
@ -130,6 +151,11 @@ class ConcreteMachine:
|
||||
memory_slots_[1].read_pointers[(c >> 14) + base] = cartridge_.data() + c;
|
||||
}
|
||||
}
|
||||
|
||||
if(!media.tapes.empty()) {
|
||||
tape_player_.set_tape(media.tapes.front());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -222,6 +248,9 @@ class ConcreteMachine:
|
||||
default: break;
|
||||
}
|
||||
|
||||
// Update the tape. (TODO: allow for sleeping)
|
||||
tape_player_.run_for(cycle.length.as_int());
|
||||
|
||||
// Per the best information I currently have, the MSX inserts an extra cycle into each opcode read,
|
||||
// but otherwise runs without pause.
|
||||
HalfCycles addition((cycle.operation == CPU::Z80::PartialMachineCycle::ReadOpcode) ? 2 : 0);
|
||||
@ -299,8 +328,8 @@ class ConcreteMachine:
|
||||
|
||||
class i8255PortHandler: public Intel::i8255::PortHandler {
|
||||
public:
|
||||
i8255PortHandler(ConcreteMachine &machine, AudioToggle &audio_toggle) :
|
||||
machine_(machine), audio_toggle_(audio_toggle) {}
|
||||
i8255PortHandler(ConcreteMachine &machine, AudioToggle &audio_toggle, Storage::Tape::BinaryTapePlayer &tape_player) :
|
||||
machine_(machine), audio_toggle_(audio_toggle), tape_player_(tape_player) {}
|
||||
|
||||
void set_value(int port, uint8_t value) {
|
||||
switch(port) {
|
||||
@ -309,7 +338,9 @@ class ConcreteMachine:
|
||||
// TODO:
|
||||
// b6 caps lock LED
|
||||
// b5 audio output
|
||||
// b4 cassette motor relay
|
||||
|
||||
// b4: cassette motor relay
|
||||
tape_player_.set_motor_control(!!(value & 0x10));
|
||||
|
||||
// b7: keyboard click
|
||||
bool new_audio_level = !!(value & 0x80);
|
||||
@ -335,6 +366,7 @@ class ConcreteMachine:
|
||||
private:
|
||||
ConcreteMachine &machine_;
|
||||
AudioToggle &audio_toggle_;
|
||||
Storage::Tape::BinaryTapePlayer &tape_player_;
|
||||
};
|
||||
|
||||
CPU::Z80::Processor<ConcreteMachine, false, false> z80_;
|
||||
@ -347,6 +379,8 @@ class ConcreteMachine:
|
||||
Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle> mixer_;
|
||||
Outputs::Speaker::LowpassSpeaker<Outputs::Speaker::CompoundSource<GI::AY38910::AY38910, AudioToggle>> speaker_;
|
||||
|
||||
Storage::Tape::BinaryTapePlayer tape_player_;
|
||||
|
||||
i8255PortHandler i8255_port_handler_;
|
||||
AYPortHandler ay_port_handler_;
|
||||
|
||||
|
@ -265,6 +265,7 @@
|
||||
<key>CFBundleTypeExtensions</key>
|
||||
<array>
|
||||
<string>cas</string>
|
||||
<string>tsx</string>
|
||||
</array>
|
||||
<key>CFBundleTypeIconFile</key>
|
||||
<string>cassette</string>
|
||||
@ -272,6 +273,8 @@
|
||||
<string>MSX Tape Image</string>
|
||||
<key>CFBundleTypeRole</key>
|
||||
<string>Viewer</string>
|
||||
<key>LSTypeIsPackage</key>
|
||||
<integer>0</integer>
|
||||
<key>NSDocumentClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).MachineDocument</string>
|
||||
</dict>
|
||||
|
@ -65,6 +65,9 @@ void StaticAnalyser::MSX::AddTargets(const Media &media, std::list<Target> &dest
|
||||
|
||||
target.media.cartridges = MSXCartridgesFrom(media.cartridges);
|
||||
|
||||
// TODO: tape parsing. Be dumb for now.
|
||||
target.media.tapes = media.tapes;
|
||||
|
||||
if(!target.media.empty()) {
|
||||
target.machine = Target::MSX;
|
||||
target.probability = 1.0;
|
||||
|
@ -114,6 +114,7 @@ static Media GetMediaAndPlatforms(const char *file_name, TargetPlatform::IntType
|
||||
Format("ssd", result.disks, Disk::DiskImageHolder<Storage::Disk::SSD>, TargetPlatform::Acorn) // SSD
|
||||
Format("tap", result.tapes, Tape::CommodoreTAP, TargetPlatform::Commodore) // TAP (Commodore)
|
||||
Format("tap", result.tapes, Tape::OricTAP, TargetPlatform::Oric) // TAP (Oric)
|
||||
Format("tsx", result.tapes, Tape::TZX, TargetPlatform::MSX) // TSX
|
||||
Format("tzx", result.tapes, Tape::TZX, TargetPlatform::ZX8081) // TZX
|
||||
Format("uef", result.tapes, Tape::UEF, TargetPlatform::Acorn) // UEF (tape)
|
||||
|
||||
|
@ -76,6 +76,8 @@ void TZX::get_next_pulses() {
|
||||
case 0x31: ignore_message_block(); break;
|
||||
case 0x33: get_hardware_type(); break;
|
||||
|
||||
case 0x4b: get_kansas_city_block(); break;
|
||||
|
||||
default:
|
||||
// In TZX each chunk has a different way of stating or implying its length,
|
||||
// so there is no route past an unimplemented chunk.
|
||||
@ -203,9 +205,7 @@ void TZX::get_turbo_speed_data_block() {
|
||||
|
||||
void TZX::get_data_block(const DataBlock &data_block) {
|
||||
// Output pilot tone.
|
||||
for(unsigned int c = 0; c < data_block.length_of_pilot_tone; c++) {
|
||||
post_pulse(data_block.length_of_pilot_pulse);
|
||||
}
|
||||
post_pulses(data_block.length_of_pilot_tone, data_block.length_of_pilot_pulse);
|
||||
|
||||
// Output sync pulses.
|
||||
post_pulse(data_block.length_of_sync_first_pulse);
|
||||
@ -237,7 +237,7 @@ void TZX::get_pure_tone_data_block() {
|
||||
uint16_t length_of_pulse = file_.get16le();
|
||||
uint16_t nunber_of_pulses = file_.get16le();
|
||||
|
||||
while(nunber_of_pulses--) post_pulse(length_of_pulse);
|
||||
post_pulses(nunber_of_pulses, length_of_pulse);
|
||||
}
|
||||
|
||||
void TZX::get_pure_data_block() {
|
||||
@ -267,8 +267,68 @@ void TZX::get_pause() {
|
||||
}
|
||||
}
|
||||
|
||||
void TZX::get_kansas_city_block() {
|
||||
uint32_t block_length = file_.get32le();
|
||||
|
||||
const uint16_t pause_after_block = file_.get16le();
|
||||
const uint16_t pilot_pulse_duration = file_.get16le();
|
||||
const uint16_t pilot_length = file_.get16le();
|
||||
uint16_t pulse_durations[2];
|
||||
pulse_durations[0] = file_.get16le();
|
||||
pulse_durations[1] = file_.get16le();
|
||||
const uint8_t packed_pulse_counts = file_.get8();
|
||||
const unsigned int pulse_counts[2] = {
|
||||
static_cast<unsigned int>((((packed_pulse_counts >> 4) - 1) & 15) + 1),
|
||||
static_cast<unsigned int>((((packed_pulse_counts & 15) - 1) & 15) + 1)
|
||||
};
|
||||
const uint8_t padding_flags = file_.get8();
|
||||
|
||||
const unsigned int number_of_leading_pulses = ((padding_flags >> 6)&3) * pulse_counts[(padding_flags >> 5) & 1];
|
||||
const unsigned int leading_pulse_length = pulse_durations[(padding_flags >> 5) & 1];
|
||||
|
||||
const unsigned int number_of_trailing_pulses = ((padding_flags >> 3)&3) * pulse_counts[(padding_flags >> 2) & 1];
|
||||
const unsigned int trailing_pulse_length = pulse_durations[(padding_flags >> 2) & 1];
|
||||
|
||||
block_length -= 12;
|
||||
|
||||
// Output pilot tone.
|
||||
post_pulses(pilot_length, pilot_pulse_duration);
|
||||
|
||||
// Output data.
|
||||
while(block_length--) {
|
||||
post_pulses(number_of_leading_pulses, leading_pulse_length);
|
||||
|
||||
uint8_t new_byte = file_.get8();
|
||||
int bits = 8;
|
||||
if(padding_flags & 1) {
|
||||
// Output MSB first.
|
||||
while(bits--) {
|
||||
int bit = (new_byte >> 7) & 1;
|
||||
new_byte <<= 1;
|
||||
post_pulses(pulse_counts[bit], pulse_durations[bit]);
|
||||
}
|
||||
} else {
|
||||
// Output LSB first.
|
||||
while(bits--) {
|
||||
int bit = new_byte & 1;
|
||||
new_byte >>= 1;
|
||||
post_pulses(pulse_counts[bit], pulse_durations[bit]);
|
||||
}
|
||||
}
|
||||
|
||||
post_pulses(number_of_trailing_pulses, trailing_pulse_length);
|
||||
}
|
||||
|
||||
// Output gap.
|
||||
post_gap(pause_after_block);
|
||||
}
|
||||
|
||||
// MARK: - Output
|
||||
|
||||
void TZX::post_pulses(unsigned int count, unsigned int length) {
|
||||
while(count--) post_pulse(length);
|
||||
}
|
||||
|
||||
void TZX::post_pulse(unsigned int length) {
|
||||
post_pulse(Storage::Time(length, StandardTZXClock));
|
||||
}
|
||||
|
@ -46,6 +46,7 @@ class TZX: public PulseQueuedTape {
|
||||
void get_pure_data_block();
|
||||
void get_generalised_data_block();
|
||||
void get_pause();
|
||||
void get_kansas_city_block();
|
||||
|
||||
void get_hardware_type();
|
||||
|
||||
@ -80,6 +81,7 @@ class TZX: public PulseQueuedTape {
|
||||
void get_data_block(const DataBlock &);
|
||||
void get_data(const Data &);
|
||||
|
||||
void post_pulses(unsigned int count, unsigned int length);
|
||||
void post_pulse(unsigned int length);
|
||||
void post_gap(unsigned int milliseconds);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user