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-2019 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// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
24Paddles::Paddles(Jack jack, const Event& event, const System& system,
25 bool swappaddle, bool swapaxis, bool swapdir)
26 : Controller(jack, event, system, Controller::Type::Paddles),
27 myMPaddleID(-1),
28 myMPaddleIDX(-1),
29 myMPaddleIDY(-1)
30{
31 // We must start with minimum resistance; see commit
32 // 38b452e1a047a0dca38c5bcce7c271d40f76736e for more information
33 setPin(AnalogPin::Five, MIN_RESISTANCE);
34 setPin(AnalogPin::Nine, MIN_RESISTANCE);
35
36 // The following logic reflects that mapping paddles to different
37 // devices can be extremely complex
38 // As well, while many paddle games have horizontal movement of
39 // objects (which maps nicely to horizontal movement of the joystick
40 // or mouse), others have vertical movement
41 // This vertical handling is taken care of by swapping the axes
42 // On the other hand, some games treat paddle resistance differently,
43 // (ie, increasing resistance can move an object right instead of left)
44 // This is taken care of by swapping the direction of movement
45 // Arrgh, did I mention that paddles are complex ...
46
47 // As much as possible, precompute which events we care about for
48 // a given port; this will speed up processing in update()
49
50 // Consider whether this is the left or right port
51 if(myJack == Jack::Left)
52 {
53 if(!swappaddle) // First paddle is 0, second is 1
54 {
55 // These aren't affected by changes in axis orientation
56 myP0AxisValue = Event::PaddleZeroAnalog;
57 myP1AxisValue = Event::PaddleOneAnalog;
58 myP0FireEvent = Event::PaddleZeroFire;
59 myP1FireEvent = Event::PaddleOneFire;
60
61 // Direction of movement is swapped
62 // That is, moving in a certain direction on an axis can
63 // result in either increasing or decreasing paddle movement
64 if(!swapdir)
65 {
66 myP0DecEvent = Event::PaddleZeroDecrease;
67 myP0IncEvent = Event::PaddleZeroIncrease;
68 myP1DecEvent = Event::PaddleOneDecrease;
69 myP1IncEvent = Event::PaddleOneIncrease;
70 }
71 else
72 {
73 myP0DecEvent = Event::PaddleZeroIncrease;
74 myP0IncEvent = Event::PaddleZeroDecrease;
75 myP1DecEvent = Event::PaddleOneIncrease;
76 myP1IncEvent = Event::PaddleOneDecrease;
77 }
78 }
79 else // First paddle is 1, second is 0
80 {
81 // These aren't affected by changes in axis orientation
82 myP0AxisValue = Event::PaddleOneAnalog;
83 myP1AxisValue = Event::PaddleZeroAnalog;
84 myP0FireEvent = Event::PaddleOneFire;
85 myP1FireEvent = Event::PaddleZeroFire;
86
87 // Direction of movement is swapped
88 // That is, moving in a certain direction on an axis can
89 // result in either increasing or decreasing paddle movement
90 if(!swapdir)
91 {
92 myP0DecEvent = Event::PaddleOneDecrease;
93 myP0IncEvent = Event::PaddleOneIncrease;
94 myP1DecEvent = Event::PaddleZeroDecrease;
95 myP1IncEvent = Event::PaddleZeroIncrease;
96 }
97 else
98 {
99 myP0DecEvent = Event::PaddleOneIncrease;
100 myP0IncEvent = Event::PaddleOneDecrease;
101 myP1DecEvent = Event::PaddleZeroIncrease;
102 myP1IncEvent = Event::PaddleZeroDecrease;
103 }
104 }
105 }
106 else // Jack is right port
107 {
108 if(!swappaddle) // First paddle is 2, second is 3
109 {
110 // These aren't affected by changes in axis orientation
111 myP0AxisValue = Event::PaddleTwoAnalog;
112 myP1AxisValue = Event::PaddleThreeAnalog;
113 myP0FireEvent = Event::PaddleTwoFire;
114 myP1FireEvent = Event::PaddleThreeFire;
115
116 // Direction of movement is swapped
117 // That is, moving in a certain direction on an axis can
118 // result in either increasing or decreasing paddle movement
119 if(!swapdir)
120 {
121 myP0DecEvent = Event::PaddleTwoDecrease;
122 myP0IncEvent = Event::PaddleTwoIncrease;
123 myP1DecEvent = Event::PaddleThreeDecrease;
124 myP1IncEvent = Event::PaddleThreeIncrease;
125 }
126 else
127 {
128 myP0DecEvent = Event::PaddleTwoIncrease;
129 myP0IncEvent = Event::PaddleTwoDecrease;
130 myP1DecEvent = Event::PaddleThreeIncrease;
131 myP1IncEvent = Event::PaddleThreeDecrease;
132 }
133 }
134 else // First paddle is 3, second is 2
135 {
136 // These aren't affected by changes in axis orientation
137 myP0AxisValue = Event::PaddleThreeAnalog;
138 myP1AxisValue = Event::PaddleTwoAnalog;
139 myP0FireEvent = Event::PaddleThreeFire;
140 myP1FireEvent = Event::PaddleTwoFire;
141
142 // Direction of movement is swapped
143 // That is, moving in a certain direction on an axis can
144 // result in either increasing or decreasing paddle movement
145 if(!swapdir)
146 {
147 myP0DecEvent = Event::PaddleThreeDecrease;
148 myP0IncEvent = Event::PaddleThreeIncrease;
149 myP1DecEvent = Event::PaddleTwoDecrease;
150 myP1IncEvent = Event::PaddleTwoIncrease;
151 }
152 else
153 {
154 myP0DecEvent = Event::PaddleThreeIncrease;
155 myP0IncEvent = Event::PaddleThreeDecrease;
156 myP1DecEvent = Event::PaddleTwoIncrease;
157 myP1IncEvent = Event::PaddleTwoDecrease;
158 }
159 }
160 }
161
162 // The following are independent of whether or not the port
163 // is left or right
164 MOUSE_SENSITIVITY = swapdir ? -abs(MOUSE_SENSITIVITY) :
165 abs(MOUSE_SENSITIVITY);
166 if(!swapaxis)
167 {
168 myAxisMouseMotion = Event::MouseAxisXValue;
169 myAxisDigitalZero = 0;
170 myAxisDigitalOne = 1;
171 }
172 else
173 {
174 myAxisMouseMotion = Event::MouseAxisYValue;
175 myAxisDigitalZero = 1;
176 myAxisDigitalOne = 0;
177 }
178
179 // Digital pins 1, 2 and 6 are not connected
180 setPin(DigitalPin::One, true);
181 setPin(DigitalPin::Two, true);
182 setPin(DigitalPin::Six, true);
183
184 // Digital emulation of analog paddle movement
185 myKeyRepeat0 = myKeyRepeat1 = false;
186 myPaddleRepeat0 = myPaddleRepeat1 = myLastAxisX = myLastAxisY = 0;
187
188 myCharge[0] = myCharge[1] = TRIGRANGE / 2;
189 myLastCharge[0] = myLastCharge[1] = 0;
190}
191
192// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
193void Paddles::update()
194{
195 setPin(DigitalPin::Three, true);
196 setPin(DigitalPin::Four, true);
197
198 // Digital events (from keyboard or joystick hats & buttons)
199 setPin(DigitalPin::Three, myEvent.get(myP1FireEvent) == 0);
200 setPin(DigitalPin::Four, myEvent.get(myP0FireEvent) == 0);
201
202 // Paddle movement is a very difficult thing to accurately emulate,
203 // since it originally came from an analog device that had very
204 // peculiar behaviour
205 // Compounding the problem is the fact that we'd like to emulate
206 // movement with 'digital' data (like from a keyboard or a digital
207 // joystick axis), but also from a mouse (relative values)
208 // and Stelladaptor-like devices (absolute analog values clamped to
209 // a certain range)
210 // And to top it all off, we don't want one devices input to conflict
211 // with the others ...
212
213 // Analog axis events from Stelladaptor-like devices
214 // These devices generate data in the range -32768 to 32767,
215 // so we have to scale appropriately
216 // Since these events are generated and stored indefinitely,
217 // we only process the first one we see (when it differs from
218 // previous values by a pre-defined amount)
219 // Otherwise, it would always override input from digital and mouse
220 bool sa_changed = false;
221 int sa_xaxis = myEvent.get(myP0AxisValue);
222 int sa_yaxis = myEvent.get(myP1AxisValue);
223 int new_val;
224
225 const double bFac[MAX_DEJITTER - MIN_DEJITTER + 1] = {
226 // higher values mean more dejitter strength
227 0, // off
228 0.50, 0.59, 0.67, 0.74, 0.80,
229 0.85, 0.89, 0.92, 0.94, 0.95
230 };
231 const double dFac[MAX_DEJITTER - MIN_DEJITTER + 1] = {
232 // lower values mean more dejitter strength
233 1, // off
234 1.0 / 181, 1.0 / 256, 1.0 / 362, 1.0 / 512, 1.0 / 724,
235 1.0 / 1024, 1.0 / 1448, 1.0 / 2048, 1.0 / 2896, 1.0 / 4096
236 };
237 const double baseFactor = bFac[DEJITTER_BASE];
238 const double diffFactor = dFac[DEJITTER_DIFF];
239
240 if(abs(myLastAxisX - sa_xaxis) > 10)
241 {
242 // dejitter, suppress small changes only
243 double dejitter = std::pow(baseFactor, abs(sa_xaxis - myLastAxisX) * diffFactor);
244 new_val = sa_xaxis * (1 - dejitter) + myLastAxisX * dejitter;
245
246 // only use new dejittered value for larger differences
247 if (abs(new_val - sa_xaxis) > 10)
248 sa_xaxis = new_val;
249
250 setPin(AnalogPin::Nine, Int32(MAX_RESISTANCE * ((32767 - Int16(sa_xaxis)) / 65536.0)));
251 sa_changed = true;
252 }
253 if(abs(myLastAxisY - sa_yaxis) > 10)
254 {
255 // dejitter, suppress small changes only
256 double dejitter = std::pow(baseFactor, abs(sa_yaxis - myLastAxisY) * diffFactor);
257 new_val = sa_yaxis * (1 - dejitter) + myLastAxisY * dejitter;
258
259 // only use new dejittered value for larger differences
260 if (abs(new_val - sa_yaxis) > 10)
261 sa_yaxis = new_val;
262
263 setPin(AnalogPin::Five, Int32(MAX_RESISTANCE * ((32767 - Int16(sa_yaxis)) / 65536.0)));
264 sa_changed = true;
265 }
266 myLastAxisX = sa_xaxis;
267 myLastAxisY = sa_yaxis;
268 if(sa_changed)
269 return;
270
271 // Mouse motion events give relative movement
272 // That is, they're only relevant if they're non-zero
273 if(myMPaddleID > -1)
274 {
275 // We're in auto mode, where a single axis is used for one paddle only
276 myCharge[myMPaddleID] = BSPF::clamp(myCharge[myMPaddleID] -
277 (myEvent.get(myAxisMouseMotion) * MOUSE_SENSITIVITY),
278 TRIGMIN, TRIGRANGE);
279 if(myEvent.get(Event::MouseButtonLeftValue) ||
280 myEvent.get(Event::MouseButtonRightValue))
281 setPin(ourButtonPin[myMPaddleID], false);
282 }
283 else
284 {
285 // Test for 'untied' mouse axis mode, where each axis is potentially
286 // mapped to a separate paddle
287 if(myMPaddleIDX > -1)
288 {
289 myCharge[myMPaddleIDX] = BSPF::clamp(myCharge[myMPaddleIDX] -
290 (myEvent.get(Event::MouseAxisXValue) * MOUSE_SENSITIVITY),
291 TRIGMIN, TRIGRANGE);
292 if(myEvent.get(Event::MouseButtonLeftValue))
293 setPin(ourButtonPin[myMPaddleIDX], false);
294 }
295 if(myMPaddleIDY > -1)
296 {
297 myCharge[myMPaddleIDY] = BSPF::clamp(myCharge[myMPaddleIDY] -
298 (myEvent.get(Event::MouseAxisYValue) * MOUSE_SENSITIVITY),
299 TRIGMIN, TRIGRANGE);
300 if(myEvent.get(Event::MouseButtonRightValue))
301 setPin(ourButtonPin[myMPaddleIDY], false);
302 }
303 }
304
305 // Finally, consider digital input, where movement happens
306 // until a digital event is released
307 if(myKeyRepeat0)
308 {
309 myPaddleRepeat0++;
310 if(myPaddleRepeat0 > DIGITAL_SENSITIVITY)
311 myPaddleRepeat0 = DIGITAL_DISTANCE;
312 }
313 if(myKeyRepeat1)
314 {
315 myPaddleRepeat1++;
316 if(myPaddleRepeat1 > DIGITAL_SENSITIVITY)
317 myPaddleRepeat1 = DIGITAL_DISTANCE;
318 }
319
320 myKeyRepeat0 = false;
321 myKeyRepeat1 = false;
322
323 if(myEvent.get(myP0DecEvent))
324 {
325 myKeyRepeat0 = true;
326 if(myCharge[myAxisDigitalZero] > myPaddleRepeat0)
327 myCharge[myAxisDigitalZero] -= myPaddleRepeat0;
328 }
329 if(myEvent.get(myP0IncEvent))
330 {
331 myKeyRepeat0 = true;
332 if((myCharge[myAxisDigitalZero] + myPaddleRepeat0) < TRIGRANGE)
333 myCharge[myAxisDigitalZero] += myPaddleRepeat0;
334 }
335 if(myEvent.get(myP1DecEvent))
336 {
337 myKeyRepeat1 = true;
338 if(myCharge[myAxisDigitalOne] > myPaddleRepeat1)
339 myCharge[myAxisDigitalOne] -= myPaddleRepeat1;
340 }
341 if(myEvent.get(myP1IncEvent))
342 {
343 myKeyRepeat1 = true;
344 if((myCharge[myAxisDigitalOne] + myPaddleRepeat1) < TRIGRANGE)
345 myCharge[myAxisDigitalOne] += myPaddleRepeat1;
346 }
347
348 // Only change state if the charge has actually changed
349 if(myCharge[1] != myLastCharge[1])
350 setPin(AnalogPin::Five, Int32(MAX_RESISTANCE * (myCharge[1] / double(TRIGMAX))));
351 if(myCharge[0] != myLastCharge[0])
352 setPin(AnalogPin::Nine, Int32(MAX_RESISTANCE * (myCharge[0] / double(TRIGMAX))));
353
354 myLastCharge[1] = myCharge[1];
355 myLastCharge[0] = myCharge[0];
356}
357
358// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
359bool Paddles::setMouseControl(
360 Controller::Type xtype, int xid, Controller::Type ytype, int yid)
361{
362 // In 'automatic' mode, both axes on the mouse map to a single paddle,
363 // and the paddle axis and direction settings are taken into account
364 // This overrides any other mode
365 if(xtype == Controller::Type::Paddles && ytype == Controller::Type::Paddles && xid == yid)
366 {
367 myMPaddleID = ((myJack == Jack::Left && (xid == 0 || xid == 1)) ||
368 (myJack == Jack::Right && (xid == 2 || xid == 3))
369 ) ? xid & 0x01 : -1;
370 myMPaddleIDX = myMPaddleIDY = -1;
371 }
372 else
373 {
374 // The following is somewhat complex, but we need to pre-process as much
375 // as possible, so that ::update() can run quickly
376 myMPaddleID = -1;
377 if(myJack == Jack::Left && xtype == Controller::Type::Paddles)
378 {
379 myMPaddleIDX = (xid == 0 || xid == 1) ? xid & 0x01 : -1;
380 myMPaddleIDY = (yid == 0 || yid == 1) ? yid & 0x01 : -1;
381 }
382 else if(myJack == Jack::Right && ytype == Controller::Type::Paddles)
383 {
384 myMPaddleIDX = (xid == 2 || xid == 3) ? xid & 0x01 : -1;
385 myMPaddleIDY = (yid == 2 || yid == 3) ? yid & 0x01 : -1;
386 }
387 }
388
389 return true;
390}
391
392// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
393void Paddles::setDejitterBase(int strength)
394{
395 DEJITTER_BASE = BSPF::clamp(strength, MIN_DEJITTER, MAX_DEJITTER);
396}
397
398// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
399void Paddles::setDejitterDiff(int strength)
400{
401 DEJITTER_DIFF = BSPF::clamp(strength, MIN_DEJITTER, MAX_DEJITTER);
402}
403
404// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
405void Paddles::setDigitalSensitivity(int sensitivity)
406{
407 DIGITAL_SENSITIVITY = BSPF::clamp(sensitivity, 1, MAX_DIGITAL_SENSE);
408 DIGITAL_DISTANCE = 20 + (DIGITAL_SENSITIVITY << 3);
409}
410
411// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
412void Paddles::setMouseSensitivity(int sensitivity)
413{
414 MOUSE_SENSITIVITY = BSPF::clamp(sensitivity, 1, MAX_MOUSE_SENSE);
415}
416
417// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
418void Paddles::setPaddleRange(int range)
419{
420 range = BSPF::clamp(range, 1, 100);
421 TRIGRANGE = int(TRIGMAX * (range / 100.0));
422}
423
424// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
425int Paddles::TRIGRANGE = Paddles::TRIGMAX;
426int Paddles::DIGITAL_SENSITIVITY = -1;
427int Paddles::DIGITAL_DISTANCE = -1;
428int Paddles::MOUSE_SENSITIVITY = -1;
429int Paddles::DEJITTER_BASE = 0;
430int Paddles::DEJITTER_DIFF = 0;
431
432// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
433const Controller::DigitalPin Paddles::ourButtonPin[2] = {
434 DigitalPin::Four, DigitalPin::Three
435};
436