1 //============================================================================
2 //
3 // SSSS tt lll lll
4 // SS SS tt ll ll
5 // SS tttttt eeee ll ll aaaa
6 // SSSS tt ee ee ll ll aa
7 // SS tt eeeeee ll ll aaaaa -- "An Atari 2600 VCS Emulator"
8 // SS SS tt ee ll ll aa aa
9 // SSSS ttt eeeee llll llll aaaaa
10 //
11 // Copyright (c) 1995-2021 by Bradford W. Mott, Stephen Anthony
12 // and the Stella Team
13 //
14 // See the file "License.txt" for information on usage and redistribution of
15 // this file, and for a DISCLAIMER OF ALL WARRANTIES.
16 //============================================================================
17
18 #include <cmath>
19
20 #include "Event.hxx"
21 #include "Paddles.hxx"
22
23 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Paddles(Jack jack,const Event & event,const System & system,bool swappaddle,bool swapaxis,bool swapdir,bool altmap)24 Paddles::Paddles(Jack jack, const Event& event, const System& system,
25 bool swappaddle, bool swapaxis, bool swapdir, bool altmap)
26 : Controller(jack, event, system, Controller::Type::Paddles)
27 {
28 // We must start with a physical valid resistance (e.g. 0);
29 // see commit 38b452e1a047a0dca38c5bcce7c271d40f76736e for more information
30 setPin(AnalogPin::Five, AnalogReadout::connectToVcc());
31 setPin(AnalogPin::Nine, AnalogReadout::connectToVcc());
32
33 // The following logic reflects that mapping paddles to different
34 // devices can be extremely complex
35 // As well, while many paddle games have horizontal movement of
36 // objects (which maps nicely to horizontal movement of the joystick
37 // or mouse), others have vertical movement
38 // This vertical handling is taken care of by swapping the axes
39 // On the other hand, some games treat paddle resistance differently,
40 // (ie, increasing resistance can move an object right instead of left)
41 // This is taken care of by swapping the direction of movement
42 // Arrgh, did I mention that paddles are complex ...
43
44 // As much as possible, precompute which events we care about for
45 // a given port; this will speed up processing in update()
46
47 // Consider whether this is the left or right port
48 if(myJack == Jack::Left)
49 {
50 if(!altmap)
51 {
52 // First paddle is left A, second is left B
53 myAAxisValue = Event::LeftPaddleAAnalog;
54 myBAxisValue = Event::LeftPaddleBAnalog;
55 myLeftAFireEvent = Event::LeftPaddleAFire;
56 myLeftBFireEvent = Event::LeftPaddleBFire;
57
58 // These can be affected by changes in axis orientation
59 myLeftADecEvent = Event::LeftPaddleADecrease;
60 myLeftAIncEvent = Event::LeftPaddleAIncrease;
61 myLeftBDecEvent = Event::LeftPaddleBDecrease;
62 myLeftBIncEvent = Event::LeftPaddleBIncrease;
63 }
64 else
65 {
66 // First paddle is QT 3A, second is QT 3B (fire buttons only)
67 myLeftAFireEvent = Event::QTPaddle3AFire;
68 myLeftBFireEvent = Event::QTPaddle3BFire;
69
70 myAAxisValue = myBAxisValue =
71 myLeftADecEvent = myLeftAIncEvent =
72 myLeftBDecEvent = myLeftBIncEvent = Event::NoType;
73 }
74 }
75 else // Jack is right port
76 {
77 if(!altmap)
78 {
79 // First paddle is right A, second is right B
80 myAAxisValue = Event::RightPaddleAAnalog;
81 myBAxisValue = Event::RightPaddleBAnalog;
82 myLeftAFireEvent = Event::RightPaddleAFire;
83 myLeftBFireEvent = Event::RightPaddleBFire;
84
85 // These can be affected by changes in axis orientation
86 myLeftADecEvent = Event::RightPaddleADecrease;
87 myLeftAIncEvent = Event::RightPaddleAIncrease;
88 myLeftBDecEvent = Event::RightPaddleBDecrease;
89 myLeftBIncEvent = Event::RightPaddleBIncrease;
90 }
91 else
92 {
93 // First paddle is QT 4A, second is QT 4B (fire buttons only)
94 myLeftAFireEvent = Event::QTPaddle4AFire;
95 myLeftBFireEvent = Event::QTPaddle4BFire;
96
97 myAAxisValue = myBAxisValue =
98 myLeftADecEvent = myLeftAIncEvent =
99 myLeftBDecEvent = myLeftBIncEvent = Event::NoType;
100 }
101 }
102
103 // Some games swap the paddles
104 if(swappaddle)
105 {
106 // First paddle is right A|B, second is left A|B
107 swapEvents(myAAxisValue, myBAxisValue);
108 swapEvents(myLeftAFireEvent, myLeftBFireEvent);
109 swapEvents(myLeftADecEvent, myLeftBDecEvent);
110 swapEvents(myLeftAIncEvent, myLeftBIncEvent);
111 }
112
113 // Direction of movement can be swapped
114 // That is, moving in a certain direction on an axis can
115 // result in either increasing or decreasing paddle movement
116 if(swapdir)
117 {
118 swapEvents(myLeftADecEvent, myLeftAIncEvent);
119 swapEvents(myLeftBDecEvent, myLeftBIncEvent);
120 }
121
122 // The following are independent of whether or not the port
123 // is left or right
124 MOUSE_SENSITIVITY = swapdir ? -abs(MOUSE_SENSITIVITY) :
125 abs(MOUSE_SENSITIVITY);
126 if(!swapaxis)
127 {
128 myAxisMouseMotion = Event::MouseAxisXMove;
129 myAxisDigitalZero = 0;
130 myAxisDigitalOne = 1;
131 }
132 else
133 {
134 myAxisMouseMotion = Event::MouseAxisYMove;
135 myAxisDigitalZero = 1;
136 myAxisDigitalOne = 0;
137 }
138
139 // Digital pins 1, 2 and 6 are not connected
140 setPin(DigitalPin::One, true);
141 setPin(DigitalPin::Two, true);
142 setPin(DigitalPin::Six, true);
143 }
144
145 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
swapEvents(Event::Type & event1,Event::Type & event2)146 void Paddles::swapEvents(Event::Type& event1, Event::Type& event2)
147 {
148 Event::Type swappedEvent;
149
150 swappedEvent = event1;
151 event1 = event2;
152 event2 = swappedEvent;
153 }
154
155 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
update()156 void Paddles::update()
157 {
158 setPin(DigitalPin::Three, true);
159 setPin(DigitalPin::Four, true);
160
161 // Digital events (from keyboard or joystick hats & buttons)
162 bool firePressedA = myEvent.get(myLeftAFireEvent) != 0;
163 bool firePressedB = myEvent.get(myLeftBFireEvent) != 0;
164
165 // Paddle movement is a very difficult thing to accurately emulate,
166 // since it originally came from an analog device that had very
167 // peculiar behaviour
168 // Compounding the problem is the fact that we'd like to emulate
169 // movement with 'digital' data (like from a keyboard or a digital
170 // joystick axis), but also from a mouse (relative values)
171 // and Stelladaptor-like devices (absolute analog values clamped to
172 // a certain range)
173 // And to top it all off, we don't want one devices input to conflict
174 // with the others ...
175
176 if(!updateAnalogAxes())
177 {
178 updateMouse(firePressedA, firePressedB);
179 updateDigitalAxes();
180
181 // Only change state if the charge has actually changed
182 if(myCharge[1] != myLastCharge[1])
183 {
184 setPin(AnalogPin::Five, AnalogReadout::connectToVcc(MAX_RESISTANCE * (myCharge[1] / double(TRIGMAX))));
185 myLastCharge[1] = myCharge[1];
186 }
187 if(myCharge[0] != myLastCharge[0])
188 {
189 setPin(AnalogPin::Nine, AnalogReadout::connectToVcc(MAX_RESISTANCE * (myCharge[0] / double(TRIGMAX))));
190 myLastCharge[0] = myCharge[0];
191 }
192 }
193
194 setPin(DigitalPin::Four, !getAutoFireState(firePressedA));
195 setPin(DigitalPin::Three, !getAutoFireStateP1(firePressedB));
196 }
197
getReadOut(int lastAxis,int & newAxis,int center)198 AnalogReadout::Connection Paddles::getReadOut(int lastAxis, int& newAxis, int center)
199 {
200 const float range = ANALOG_RANGE - analogDeadZone() * 2;
201
202 // dead zone, ignore changes inside the dead zone
203 if(newAxis > analogDeadZone())
204 newAxis -= analogDeadZone();
205 else if(newAxis < -analogDeadZone())
206 newAxis += analogDeadZone();
207 else
208 newAxis = 0; // treat any dead zone value as zero
209
210 static constexpr std::array<float, MAX_DEJITTER - MIN_DEJITTER + 1> bFac = {
211 // higher values mean more dejitter strength
212 0.f, // off
213 0.50f, 0.59f, 0.67f, 0.74f, 0.80f,
214 0.85f, 0.89f, 0.92f, 0.94f, 0.95f
215 };
216 static constexpr std::array<float, MAX_DEJITTER - MIN_DEJITTER + 1> dFac = {
217 // lower values mean more dejitter strength
218 1.f, // off
219 1.0f / 181, 1.0f / 256, 1.0f / 362, 1.0f / 512, 1.0f / 724,
220 1.0f / 1024, 1.0f / 1448, 1.0f / 2048, 1.0f / 2896, 1.0f / 4096
221 };
222 const float baseFactor = bFac[DEJITTER_BASE];
223 const float diffFactor = dFac[DEJITTER_DIFF];
224
225 // dejitter, suppress small changes only
226 float dejitter = powf(baseFactor, std::abs(newAxis - lastAxis) * diffFactor);
227 int newVal = newAxis * (1 - dejitter) + lastAxis * dejitter;
228
229 // only use new dejittered value for larger differences
230 if(abs(newVal - newAxis) > 10)
231 newAxis = newVal;
232
233 // apply linearity
234 float linearVal = newAxis / (range / 2); // scale to -1.0..+1.0
235
236 if(newAxis >= 0)
237 linearVal = powf(std::abs(linearVal), LINEARITY);
238 else
239 linearVal = -powf(std::abs(linearVal), LINEARITY);
240
241 newAxis = linearVal * (range / 2); // scale back to ANALOG_RANGE
242
243 // scale axis to range including dead zone
244 const Int32 scaledAxis = newAxis * ANALOG_RANGE / range;
245
246 // scale result
247 return AnalogReadout::connectToVcc(MAX_RESISTANCE *
248 BSPF::clamp((ANALOG_MAX_VALUE - (scaledAxis * SENSITIVITY + center)) /
249 float(ANALOG_RANGE), 0.F, 1.F));
250 }
251
252 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateAnalogAxes()253 bool Paddles::updateAnalogAxes()
254 {
255 // Analog axis events from Stelladaptor-like devices,
256 // (which includes analog USB controllers)
257 // These devices generate data in the range -32768 to 32767,
258 // so we have to scale appropriately
259 // Since these events are generated and stored indefinitely,
260 // we only process the first one we see (when it differs from
261 // previous values by a pre-defined amount)
262 // Otherwise, it would always override input from digital and mouse
263
264
265 int sa_xaxis = myEvent.get(myAAxisValue);
266 int sa_yaxis = myEvent.get(myBAxisValue);
267 bool sa_changed = false;
268
269 if(abs(myLastAxisX - sa_xaxis) > 10)
270 {
271 setPin(AnalogPin::Nine, getReadOut(myLastAxisX, sa_xaxis, XCENTER));
272 sa_changed = true;
273 }
274
275 if(abs(myLastAxisY - sa_yaxis) > 10)
276 {
277 setPin(AnalogPin::Five, getReadOut(myLastAxisY, sa_yaxis, YCENTER));
278 sa_changed = true;
279 }
280 myLastAxisX = sa_xaxis;
281 myLastAxisY = sa_yaxis;
282
283 return sa_changed;
284 }
285
286 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateMouse(bool & firePressedA,bool & firePressedB)287 void Paddles::updateMouse(bool& firePressedA, bool& firePressedB)
288 {
289 // Mouse motion events give relative movement
290 // That is, they're only relevant if they're non-zero
291 if(myMPaddleID > -1)
292 {
293 // We're in auto mode, where a single axis is used for one paddle only
294 myCharge[myMPaddleID] = BSPF::clamp(myCharge[myMPaddleID] -
295 (myEvent.get(myAxisMouseMotion) * MOUSE_SENSITIVITY),
296 TRIGMIN, TRIGRANGE);
297
298 if(myMPaddleID == 0)
299 firePressedA = firePressedA
300 || myEvent.get(Event::MouseButtonLeftValue)
301 || myEvent.get(Event::MouseButtonRightValue);
302 else
303 firePressedB = firePressedB
304 || myEvent.get(Event::MouseButtonLeftValue)
305 || myEvent.get(Event::MouseButtonRightValue);
306 }
307 else
308 {
309 // Test for 'untied' mouse axis mode, where each axis is potentially
310 // mapped to a separate paddle
311 if(myMPaddleIDX > -1)
312 {
313 myCharge[myMPaddleIDX] = BSPF::clamp(myCharge[myMPaddleIDX] -
314 (myEvent.get(Event::MouseAxisXMove) * MOUSE_SENSITIVITY),
315 TRIGMIN, TRIGRANGE);
316 if(myMPaddleIDX == 0)
317 firePressedA = firePressedA
318 || myEvent.get(Event::MouseButtonLeftValue);
319 else
320 firePressedB = firePressedB
321 || myEvent.get(Event::MouseButtonLeftValue);
322 }
323 if(myMPaddleIDY > -1)
324 {
325 myCharge[myMPaddleIDY] = BSPF::clamp(myCharge[myMPaddleIDY] -
326 (myEvent.get(Event::MouseAxisYMove) * MOUSE_SENSITIVITY),
327 TRIGMIN, TRIGRANGE);
328 if(myMPaddleIDY == 0)
329 firePressedA = firePressedA
330 || myEvent.get(Event::MouseButtonRightValue);
331 else
332 firePressedB = firePressedB
333 || myEvent.get(Event::MouseButtonRightValue);
334 }
335 }
336 }
337
338 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
updateDigitalAxes()339 void Paddles::updateDigitalAxes()
340 {
341 // Finally, consider digital input, where movement happens
342 // until a digital event is released
343 if(myKeyRepeatA)
344 {
345 myPaddleRepeatA++;
346 if(myPaddleRepeatA > DIGITAL_SENSITIVITY)
347 myPaddleRepeatA = DIGITAL_DISTANCE;
348 }
349 if(myKeyRepeatB)
350 {
351 myPaddleRepeatB++;
352 if(myPaddleRepeatB > DIGITAL_SENSITIVITY)
353 myPaddleRepeatB = DIGITAL_DISTANCE;
354 }
355
356 myKeyRepeatA = false;
357 myKeyRepeatB = false;
358
359 if(myEvent.get(myLeftADecEvent))
360 {
361 myKeyRepeatA = true;
362 if(myCharge[myAxisDigitalZero] > myPaddleRepeatA)
363 myCharge[myAxisDigitalZero] -= myPaddleRepeatA;
364 }
365 if(myEvent.get(myLeftAIncEvent))
366 {
367 myKeyRepeatA = true;
368 if((myCharge[myAxisDigitalZero] + myPaddleRepeatA) < TRIGRANGE)
369 myCharge[myAxisDigitalZero] += myPaddleRepeatA;
370 }
371 if(myEvent.get(myLeftBDecEvent))
372 {
373 myKeyRepeatB = true;
374 if(myCharge[myAxisDigitalOne] > myPaddleRepeatB)
375 myCharge[myAxisDigitalOne] -= myPaddleRepeatB;
376 }
377 if(myEvent.get(myLeftBIncEvent))
378 {
379 myKeyRepeatB = true;
380 if((myCharge[myAxisDigitalOne] + myPaddleRepeatB) < TRIGRANGE)
381 myCharge[myAxisDigitalOne] += myPaddleRepeatB;
382 }
383 }
384
385 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setMouseControl(Controller::Type xtype,int xid,Controller::Type ytype,int yid)386 bool Paddles::setMouseControl(
387 Controller::Type xtype, int xid, Controller::Type ytype, int yid)
388 {
389 // In 'automatic' mode, both axes on the mouse map to a single paddle,
390 // and the paddle axis and direction settings are taken into account
391 // This overrides any other mode
392 if(xtype == Controller::Type::Paddles && ytype == Controller::Type::Paddles && xid == yid)
393 {
394 myMPaddleID = ((myJack == Jack::Left && (xid == 0 || xid == 1)) ||
395 (myJack == Jack::Right && (xid == 2 || xid == 3))
396 ) ? xid & 0x01 : -1;
397 myMPaddleIDX = myMPaddleIDY = -1;
398 }
399 else
400 {
401 // The following is somewhat complex, but we need to pre-process as much
402 // as possible, so that ::update() can run quickly
403 myMPaddleID = -1;
404 if(myJack == Jack::Left)
405 {
406 if(xtype == Controller::Type::Paddles)
407 myMPaddleIDX = (xid == 0 || xid == 1) ? xid & 0x01 : -1;
408 if(ytype == Controller::Type::Paddles)
409 myMPaddleIDY = (yid == 0 || yid == 1) ? yid & 0x01 : -1;
410 }
411 else if(myJack == Jack::Right)
412 {
413 if(xtype == Controller::Type::Paddles)
414 myMPaddleIDX = (xid == 2 || xid == 3) ? xid & 0x01 : -1;
415 if(ytype == Controller::Type::Paddles)
416 myMPaddleIDY = (yid == 2 || yid == 3) ? yid & 0x01 : -1;
417 }
418 }
419
420 return true;
421 }
422
423 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setAnalogXCenter(int xcenter)424 void Paddles::setAnalogXCenter(int xcenter)
425 {
426 // convert into ~5 pixel steps
427 XCENTER = BSPF::clamp(xcenter, MIN_ANALOG_CENTER, MAX_ANALOG_CENTER) * 860;
428 }
429
430 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setAnalogYCenter(int ycenter)431 void Paddles::setAnalogYCenter(int ycenter)
432 {
433 // convert into ~5 pixel steps
434 YCENTER = BSPF::clamp(ycenter, MIN_ANALOG_CENTER, MAX_ANALOG_CENTER) * 860;
435 }
436
437 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setAnalogSensitivity(int sensitivity)438 float Paddles::setAnalogSensitivity(int sensitivity)
439 {
440 return SENSITIVITY = analogSensitivityValue(sensitivity);
441 }
442
443 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
analogSensitivityValue(int sensitivity)444 float Paddles::analogSensitivityValue(int sensitivity)
445 {
446 // BASE_ANALOG_SENSE * (1.1 ^ 20) = 1.0
447 return BASE_ANALOG_SENSE * std::pow(1.1F,
448 static_cast<float>(BSPF::clamp(sensitivity, MIN_ANALOG_SENSE, MAX_ANALOG_SENSE)));
449 }
450
451 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setAnalogLinearity(int linearity)452 void Paddles::setAnalogLinearity(int linearity)
453 {
454 LINEARITY = 100.f / BSPF::clamp(linearity, MIN_ANALOG_LINEARITY, MAX_ANALOG_LINEARITY);
455 }
456 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setDejitterBase(int strength)457 void Paddles::setDejitterBase(int strength)
458 {
459 DEJITTER_BASE = BSPF::clamp(strength, MIN_DEJITTER, MAX_DEJITTER);
460 }
461
462 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setDejitterDiff(int strength)463 void Paddles::setDejitterDiff(int strength)
464 {
465 DEJITTER_DIFF = BSPF::clamp(strength, MIN_DEJITTER, MAX_DEJITTER);
466 }
467
468 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setDigitalSensitivity(int sensitivity)469 void Paddles::setDigitalSensitivity(int sensitivity)
470 {
471 DIGITAL_SENSITIVITY = BSPF::clamp(sensitivity, MIN_DIGITAL_SENSE, MAX_DIGITAL_SENSE);
472 DIGITAL_DISTANCE = 20 + (DIGITAL_SENSITIVITY << 3);
473 }
474
475 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
setDigitalPaddleRange(int range)476 void Paddles::setDigitalPaddleRange(int range)
477 {
478 range = BSPF::clamp(range, MIN_MOUSE_RANGE, MAX_MOUSE_RANGE);
479 TRIGRANGE = int(TRIGMAX * (range / 100.0));
480 }
481
482 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
483 int Paddles::XCENTER = 0;
484 int Paddles::YCENTER = 0;
485 float Paddles::SENSITIVITY = 1.0;
486 float Paddles::LINEARITY = 1.0;
487 int Paddles::DEJITTER_BASE = 0;
488 int Paddles::DEJITTER_DIFF = 0;
489 int Paddles::TRIGRANGE = Paddles::TRIGMAX;
490
491 int Paddles::DIGITAL_SENSITIVITY = -1;
492 int Paddles::DIGITAL_DISTANCE = -1;
493