diff --git a/Analyser/Static/Thomson/Target.hpp b/Analyser/Static/Thomson/Target.hpp index 0dfa3ac75..4b71be833 100644 --- a/Analyser/Static/Thomson/Target.hpp +++ b/Analyser/Static/Thomson/Target.hpp @@ -21,7 +21,7 @@ struct MOTarget: public Analyser::Static::Target, public Reflection::StructImpl< ReflectableEnum(Floppy, None, CD90_640); Floppy floppy = Floppy::None; - ReflectableEnum(Model, MO5v1, MO5v11, MO6v1, MO6v2, MO6v3); + ReflectableEnum(Model, MO5v1, MO5v11, MO6v1, MO6v2, MO6v3, Prodest128); Model model = Model::MO5v11; MOTarget() : Analyser::Static::Target(Machine::ThomsonMO) {} diff --git a/Machines/Thomson/MO/Keyboard.hpp b/Machines/Thomson/MO/Keyboard.hpp index 4d6a32b4f..02143da69 100644 --- a/Machines/Thomson/MO/Keyboard.hpp +++ b/Machines/Thomson/MO/Keyboard.hpp @@ -14,7 +14,7 @@ namespace Thomson::MO::Keyboard { enum class Machine { - MO5, MO6 + MO5, MO6, Prodest128 }; namespace MO5 { @@ -214,6 +214,111 @@ static inline std::unordered_map> sequences }; } +namespace Prodest128 { +enum Key: uint16_t { + k0 = 0x1e, k1 = 0x2f, k2 = 0x27, k3 = 0x1f, + k4 = 0x17, k5 = 0x0f, k6 = 0x07, k7 = 0x06, + k8 = 0x0e, k9 = 0x16, + + Q = 0x2d, W = 0x25, E = 0x1d, R = 0x15, + T = 0x0d, Y = 0x05, U = 0x04, I = 0x0c, + O = 0x14, P = 0x1c, A = 0x2b, S = 0x23, + D = 0x1b, F = 0x13, G = 0x0b, H = 0x03, + J = 0x02, K = 0x0a, L = 0x12, Z = 0x30, + X = 0x28, C = 0x32, V = 0x2a, B = 0x22, + N = 0x00, M = 0x08, NWithLine = 0x1a, + + OpenSquareBracket = 0x18, + CCedilla = 0x50, // ç + Quote = 0x26, + CloseSquareBracket = 0x2e, + ACC = 0x36, + + Stop = 0x37, + InvertedQuestionMark = 0x58, + Plus = 0x2c, + + CloseAngleBracket = 0x40, + InvertedExclamationMark = 0x60, + Hash = 0x48, + + Comma = 0x10, + Dot = 0x24, + Minus = 0x11, + RAZ = 0x33, + INS = 0x09, + EFF = 0x01, + + Enter = 0x34, + Space = 0x20, + + Up = 0x31, + Down = 0x21, + Left = 0x29, + Right = 0x19, + + F1 = 0x3b, + F2 = 0x3c, + F3 = 0x3d, + F4 = 0x3e, + F5 = 0x3f, + + ShiftLock = 0x3a, + BASIC = 0x39, + Shift = 0x38, + Control = 0x35, +}; + +static inline std::unordered_map> sequences = { + {L'\n', {Enter}}, {L'\r', {Enter}}, + {L' ', {Space}}, + + {L'0', {k0}}, {L'1', {k1}}, {L'2', {k2}}, {L'3', {k3}}, {L'4', {k4}}, + {L'5', {k5}}, {L'6', {k6}}, {L'7', {k7}}, {L'8', {k8}}, {L'9', {k9}}, + + {L'A', {Shift, A}}, {L'B', {Shift, B}}, {L'C', {Shift, C}}, + {L'D', {Shift, D}}, {L'E', {Shift, E}}, {L'F', {Shift, F}}, + {L'G', {Shift, G}}, {L'H', {Shift, H}}, {L'I', {Shift, I}}, + {L'J', {Shift, J}}, {L'K', {Shift, K}}, {L'L', {Shift, L}}, + {L'M', {Shift, M}}, {L'N', {Shift, N}}, {L'O', {Shift, O}}, + {L'P', {Shift, P}}, {L'Q', {Shift, Q}}, {L'R', {Shift, R}}, + {L'S', {Shift, S}}, {L'T', {Shift, T}}, {L'U', {Shift, U}}, + {L'V', {Shift, V}}, {L'W', {Shift, W}}, {L'X', {Shift, X}}, + {L'Y', {Shift, Y}}, {L'Z', {Shift, Z}}, + + {L'a', {A}}, {L'b', {B}}, {L'c', {C}}, + {L'd', {D}}, {L'e', {E}}, {L'f', {F}}, + {L'g', {G}}, {L'h', {H}}, {L'i', {I}}, + {L'j', {J}}, {L'k', {K}}, {L'l', {L}}, + {L'm', {M}}, {L'n', {N}}, {L'o', {O}}, + {L'p', {P}}, {L'q', {Q}}, {L'r', {R}}, + {L's', {S}}, {L't', {T}}, {L'u', {U}}, + {L'v', {V}}, {L'w', {W}}, {L'x', {X}}, + {L'y', {Y}}, {L'z', {Z}}, + + {L'{', {Shift, OpenSquareBracket}}, {L'[', {OpenSquareBracket}}, + {L'=', {Shift, k0}}, {L'!', {Shift, k1}}, {L'\"', {Shift, k2}}, {L'§', {Shift, k3}}, {L'$', {Shift, k4}}, + {L'%', {Shift, k5}}, {L'&', {Shift, k6}}, {L'/', {Shift, k7}}, {L'(', {Shift, k8}}, {L')', {Shift, k9}}, + + {L'?', {Shift, CCedilla}}, {L'ç', {CCedilla}}, + {L'£', {Shift, Quote}}, {L'\'', {Quote}}, {L'`', {RAZ, Quote}}, + {L'}', {Shift, CloseSquareBracket}}, {L']', {CloseSquareBracket}}, + + {L'\\', {Shift, Stop}}, + + {L'@', {Shift, InvertedQuestionMark}}, {L'¿', {InvertedQuestionMark}}, + {L'*', {Shift, Plus}}, {L'+', {Plus}}, + + {L'<', {Shift, CloseAngleBracket}}, {L'>', {CloseAngleBracket}}, + {L'¨', {Shift, InvertedExclamationMark}}, {L'¡', {InvertedExclamationMark}}, + {L'^', {Shift, Hash}}, {L'#', {Hash}}, + + {L';', {Shift, Comma}}, {L',', {Comma}}, + {L':', {Shift, Dot}}, {L'.', {Dot}}, + {L'_', {Shift, Minus}}, {L'-', {Minus}}, +}; +} + struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMapper { KeyboardMapper(const Machine machine) : machine_(machine) {} @@ -306,7 +411,8 @@ struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMappe case In::V: return V; case In::B: return B; case In::N: return N; case In::M: return M; - case In::Space: return Space; + case In::Space: return Space; + case In::Enter: return Enter; case In::BackTick: return Hash; case In::Backslash: return CloseBracket; @@ -332,9 +438,80 @@ struct KeyboardMapper: public MachineTypes::MappedKeyboardMachine::KeyboardMappe case In::Insert: return INS; case In::Delete: return EFF; + case In::Tab: return Stop; + + case In::LeftShift: + case In::RightShift: return Shift; + case In::LeftControl: + case In::RightControl: return Control; + case In::LeftOption: + case In::RightOption: return BASIC; + case In::LeftMeta: + case In::RightMeta: return RAZ; + case In::CapsLock: return ShiftLock; + + case In::F1: return F1; + case In::F2: return F2; + case In::F3: return F3; + case In::F4: return F4; + case In::F5: return F5; + } + break; + + case Machine::Prodest128: + switch(key) { + default: return MachineTypes::MappedKeyboardMachine::KeyNotMapped; + + using enum Prodest128::Key; + case In::k0: return k0; case In::k1: return k1; + case In::k2: return k2; case In::k3: return k3; + case In::k4: return k4; case In::k5: return k5; + case In::k6: return k6; case In::k7: return k7; + case In::k8: return k8; case In::k9: return k9; + + case In::Q: return Q; case In::W: return W; + case In::E: return E; case In::R: return R; + case In::T: return T; case In::Y: return Y; + case In::U: return U; case In::I: return I; + case In::O: return O; case In::P: return P; + case In::A: return A; case In::S: return S; + case In::D: return D; case In::F: return F; + case In::G: return G; case In::H: return H; + case In::J: return J; case In::K: return K; + case In::L: return L; case In::Z: return Z; + case In::X: return X; case In::C: return C; + case In::V: return V; case In::B: return B; + case In::N: return N; case In::M: return M; + + case In::Space: return Space; + case In::Enter: return Enter; + + case In::BackTick: return OpenSquareBracket; + case In::Hyphen: return CCedilla; + case In::Equals: return Quote; + case In::Backslash: return CloseSquareBracket; + + case In::Tab: return Stop; + + case In::OpenSquareBracket: return InvertedQuestionMark; + case In::CloseSquareBracket: return Plus; + + case In::Semicolon: return NWithLine; + case In::Quote: return InvertedExclamationMark; + + case In::Comma: return Comma; + case In::FullStop: return Dot; + case In::ForwardSlash: return Minus; + + case In::Up: return Up; case In::Down: return Down; + case In::Left: return Left; case In::Right: return Right; + + case In::Backspace: return ACC; + case In::Insert: return INS; + case In::Delete: return EFF; + case In::LeftShift: case In::RightShift: return Shift; - case In::Tab: case In::LeftControl: case In::RightControl: return Control; case In::LeftOption: @@ -363,7 +540,15 @@ struct CharacterMapper: public ::Utility::CharacterMapper { CharacterMapper(const Machine machine) : machine_(machine) {} std::span sequence_for_character(const wchar_t character) const final { - return lookup_sequence(machine_ == Machine::MO6 ? MO6::sequences : MO5::sequences, character); + const auto sequences = [&]() -> const std::unordered_map> & { + switch(machine_) { + case Machine::MO5: return MO5::sequences; + case Machine::MO6: return MO6::sequences; + case Machine::Prodest128: return Prodest128::sequences; + default: __builtin_unreachable(); + } + }; + return lookup_sequence(sequences(), character); } private: diff --git a/Machines/Thomson/MO/MO.cpp b/Machines/Thomson/MO/MO.cpp index 16650bafa..c4f11bffd 100644 --- a/Machines/Thomson/MO/MO.cpp +++ b/Machines/Thomson/MO/MO.cpp @@ -39,6 +39,20 @@ static constexpr uint8_t MusicExpansionMask = 63; using Target = Analyser::Static::Thomson::MOTarget; +constexpr Keyboard::Machine machine(const Target::Model model) { + switch(model) { + case Target::Model::MO5v1: + case Target::Model::MO5v11: + return Keyboard::Machine::MO5; + case Target::Model::MO6v1: + case Target::Model::MO6v2: + case Target::Model::MO6v3: + return Keyboard::Machine::MO6; + case Target::Model::Prodest128: + return Keyboard::Machine::Prodest128; + } +} + template struct ConcreteMachine: public Activity::Source, @@ -54,7 +68,7 @@ struct ConcreteMachine: public Utility::TypeRecipient { ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) : - Utility::TypeRecipient(is_mo6 ? Thomson::MO::Keyboard::Machine::MO6 : Thomson::MO::Keyboard::Machine::MO5), + Utility::TypeRecipient(machine(target.model)), m6809_(*this), system_pia_port_handler_(*this), system_pia_(system_pia_port_handler_), @@ -64,7 +78,7 @@ struct ConcreteMachine: tape_player_(ClockRate), audio_(audio_queue_, MusicExpansionMask), speaker_(audio_), - keyboard_mapper_(is_mo6 ? Thomson::MO::Keyboard::Machine::MO6 : Thomson::MO::Keyboard::Machine::MO5) + keyboard_mapper_(machine(target.model)) { set_clock_rate(ClockRate); speaker_.set_input_rate(ClockRate); @@ -74,12 +88,13 @@ struct ConcreteMachine: switch(model) { using enum Target::Model; - default: __builtin_unreachable(); - case MO5v1: return ROM::Name::ThomsonMO5v1; - case MO5v11: return ROM::Name::ThomsonMO5v11; - case MO6v1: return ROM::Name::ThomsonMO6v1; - case MO6v2: return ROM::Name::ThomsonMO6v2; - case MO6v3: return ROM::Name::ThomsonMO6v3; + default: __builtin_unreachable(); + case MO5v1: return ROM::Name::ThomsonMO5v1; + case MO5v11: return ROM::Name::ThomsonMO5v11; + case MO6v1: return ROM::Name::ThomsonMO6v1; + case MO6v2: return ROM::Name::ThomsonMO6v2; + case MO6v3: return ROM::Name::ThomsonMO6v3; + case Prodest128: return ROM::Name::OlivettiProdest128; } } (target.model); diff --git a/Machines/Utility/ROMCatalogue.cpp b/Machines/Utility/ROMCatalogue.cpp index 4625eb230..70ec8e17f 100644 --- a/Machines/Utility/ROMCatalogue.cpp +++ b/Machines/Utility/ROMCatalogue.cpp @@ -1060,6 +1060,14 @@ const std::vector &Description::all_roms() { 64_kb, 0x7a012322u }, + { + OlivettiProdest128, + "Thomson", + "the Olivetti Prodest 127 ROM", + "pc128.rom", + 64_kb, + 0xfc173504u + }, { ThomsonCD90_640, diff --git a/Machines/Utility/ROMCatalogue.hpp b/Machines/Utility/ROMCatalogue.hpp index ee01ea42b..ffc7c080e 100644 --- a/Machines/Utility/ROMCatalogue.hpp +++ b/Machines/Utility/ROMCatalogue.hpp @@ -181,6 +181,7 @@ enum Name { ThomsonMO6v1, ThomsonMO6v2, ThomsonMO6v3, + OlivettiProdest128, ThomsonCD90_640, diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h index 9adb1cf01..41ebb6111 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.h @@ -157,6 +157,7 @@ typedef NS_ENUM(NSInteger, CSPCCompatibleVideoAdaptor) { typedef NS_ENUM(NSInteger, CSMachineThomsonModel) { CSMachineThomsonModelMO5, CSMachineThomsonModelMO6, + CSMachineThomsonModelProdest128, }; typedef int Kilobytes; diff --git a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm index c771ef454..d266d8577 100644 --- a/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm +++ b/OSBindings/Mac/Clock Signal/Machine/StaticAnalyser/CSStaticAnalyser.mm @@ -552,7 +552,14 @@ PermissionDelegate permission_delegate; if(self) { using Target = Analyser::Static::Thomson::MOTarget; auto target = std::make_unique(); - target->model = model == CSMachineThomsonModelMO5 ? Target::Model::MO5v11 : Target::Model::MO6v3; + target->model = [&]() { + switch(model) { + case CSMachineThomsonModelMO5: return Target::Model::MO5v11; + case CSMachineThomsonModelMO6: return Target::Model::MO6v3; + case CSMachineThomsonModelProdest128: return Target::Model::Prodest128; + default: __builtin_unreachable(); + } + } (); target->floppy = hasDiskDrive ? Target::Floppy::CD90_640 : Target::Floppy::None; _targets.push_back(std::move(target)); } diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib index 382ec7ffa..06254a077 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib +++ b/OSBindings/Mac/Clock Signal/MachinePicker/Base.lproj/MachinePicker.xib @@ -1016,7 +1016,7 @@ Gw - + @@ -1024,6 +1024,7 @@ Gw + diff --git a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift index c66846e24..d756e641e 100644 --- a/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift +++ b/OSBindings/Mac/Clock Signal/MachinePicker/MachinePicker.swift @@ -495,6 +495,7 @@ class MachinePicker: NSObject, NSTableViewDataSource, NSTableViewDelegate, NSPat switch thomsonModelTypeButton.selectedTag() { case 5: model = .MO5 case 6: model = .MO6 + case 128: model = .prodest128 default: break } return CSStaticAnalyser(thomsonMOModel: model, hasDiskDrive: thomsonDiskButton.state == .on) diff --git a/Storage/Tape/Formats/LEP.cpp b/Storage/Tape/Formats/LEP.cpp index d9548653b..7cf01defa 100644 --- a/Storage/Tape/Formats/LEP.cpp +++ b/Storage/Tape/Formats/LEP.cpp @@ -27,10 +27,10 @@ Aside: since both +127 and -127 can be stored in a byte, it's unclear to me how this special case adds anything. It's therefore possible that machine translation has failed here. - On "units of time" the documentation says merely: + On "units of time" the documentation says opaquely: "This duration N is expressed in the unit specified as a parameter" - So it isn't obviously captured in the file itself. Great work! I've picked an empirical value below. + But I think it's usually or always 50µs. */ using namespace Storage::Tape; @@ -44,8 +44,8 @@ std::unique_ptr LEP::format_serialiser() const { LEP::Serialiser::Serialiser(const std::string &name) : file_(name, FileMode::Read) { // Empirically: a length of about 16 in a LEP means "a full pulse", i.e. 833µs or thereabouts. // - // So this scales 16 pulses to be exactly 1/1200th of a second. - pulse_.length.clock_rate = 16 * 1200; + // That seems to gel with the 50µs clock precision guess. + pulse_.length.clock_rate = 20'000; } void LEP::Serialiser::reset() {