Merge pull request #1777 from TomHarte/Prodest128

Support Olivetti Prodest 128.
This commit is contained in:
Thomas Harte authored and GitHub committed 2026-04-20 21:34:48 -04:00
commit fb708b76d0
10 files changed
+238 -19

No files matched your search

+1 -1
View File
@@ -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) {}
+189 -4
View File
@@ -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<wchar_t, const std::vector<uint16_t>> 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<wchar_t, const std::vector<uint16_t>> 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<const uint16_t> 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<wchar_t, const std::vector<uint16_t>> & {
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:
+23 -8
View File
@@ -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 <bool has_floppy, bool is_mo6>
struct ConcreteMachine:
public Activity::Source,
@@ -54,7 +68,7 @@ struct ConcreteMachine:
public Utility::TypeRecipient<Thomson::MO::Keyboard::CharacterMapper>
{
ConcreteMachine(const Target &target, const ROMMachine::ROMFetcher &rom_fetcher) :
Utility::TypeRecipient<Thomson::MO::Keyboard::CharacterMapper>(is_mo6 ? Thomson::MO::Keyboard::Machine::MO6 : Thomson::MO::Keyboard::Machine::MO5),
Utility::TypeRecipient<Thomson::MO::Keyboard::CharacterMapper>(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);
+8
View File
@@ -1060,6 +1060,14 @@ const std::vector<Description> &Description::all_roms() {
64_kb,
0x7a012322u
},
{
OlivettiProdest128,
"Thomson",
"the Olivetti Prodest 127 ROM",
"pc128.rom",
64_kb,
0xfc173504u
},
{
ThomsonCD90_640,
+1
View File
@@ -181,6 +181,7 @@ enum Name {
ThomsonMO6v1,
ThomsonMO6v2,
ThomsonMO6v3,
OlivettiProdest128,
ThomsonCD90_640,
@@ -157,6 +157,7 @@ typedef NS_ENUM(NSInteger, CSPCCompatibleVideoAdaptor) {
typedef NS_ENUM(NSInteger, CSMachineThomsonModel) {
CSMachineThomsonModelMO5,
CSMachineThomsonModelMO6,
CSMachineThomsonModelProdest128,
};
typedef int Kilobytes;
@@ -552,7 +552,14 @@ PermissionDelegate permission_delegate;
if(self) {
using Target = Analyser::Static::Thomson::MOTarget;
auto target = std::make_unique<Target>();
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));
}
@@ -1016,7 +1016,7 @@ Gw
</textFieldCell>
</textField>
<popUpButton verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="FoB-T7-dKp">
<rect key="frame" x="80" y="264" width="69" height="25"/>
<rect key="frame" x="80" y="264" width="158" height="25"/>
<popUpButtonCell key="cell" type="push" title="MO5" bezelStyle="rounded" alignment="left" lineBreakMode="truncatingTail" state="on" borderStyle="borderAndBezel" tag="5" imageScaling="proportionallyDown" inset="2" selectedItem="wjb-gR-57f" id="a51-DE-sYL">
<behavior key="behavior" lightByBackground="YES" lightByGray="YES"/>
<font key="font" metaFont="message"/>
@@ -1024,6 +1024,7 @@ Gw
<items>
<menuItem title="MO5" state="on" tag="5" id="wjb-gR-57f"/>
<menuItem title="MO6" tag="6" id="8Hf-Pj-AM3"/>
<menuItem title="Olivetti Prodest 128" tag="128" id="7GI-C0-Snt"/>
</items>
</menu>
</popUpButtonCell>
@@ -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)
+4 -4
View File
@@ -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<FormatSerialiser> 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() {