FlashingLights.java
1:
2:
3:
4:
5:
6:
7:
8:
9:
10:
11:
12:
13: package xeij;
14:
15: import java.awt.event.*;
16: import java.util.*;
17: import javax.swing.*;
18: import javax.swing.event.*;
19:
20:
21:
22:
23: public class FlashingLights {
24:
25: public static final boolean FLR_ON = true;
26: public static final boolean FLR_DEBUG = false;
27:
28:
29: public static final boolean FLR_ENABLED = false;
30: public static final boolean FLR_PALETTE = true;
31: public static final boolean FLR_CONTRAST = true;
32: public static final boolean FLR_SCREEN = true;
33: public static final int FLR_LIMIT = 13;
34: public static final int FLR_WAVELENGTH = 20;
35: public static final int FLR_DELAY = 20;
36: public static final boolean FLR_INFORMED = false;
37:
38:
39:
40: public static boolean flrRequestEnabled;
41: public static boolean flrRequestPalette;
42: public static boolean flrContrast;
43: public static boolean flrRequestScreen;
44: public static int flrWavelength;
45: public static int flrLimit;
46: public static int flrDelay;
47: public static boolean flrInformed;
48:
49:
50: public static boolean flrEnabled;
51: public static boolean flrPalette;
52: public static boolean flrScreen;
53:
54:
55: public static boolean flrActive;
56:
57:
58: public static boolean flrDebugLocation;
59:
60:
61:
62:
63:
64: public static int flrTime;
65:
66:
67:
68: public static final int[] flrGhostG8 = new int[256];
69: public static final int[] flrGhostTS = new int[256];
70:
71:
72: public static final int[] flrHistoryG8 = new int[4 * 256];
73: public static final int[] flrHistoryTS = new int[4 * 256];
74:
75:
76:
77: public static final int[] flrIndex = new int[256];
78:
79:
80:
81: public static final int FLR_RING_SIZE = 64;
82: public static final int[] flrRing = new int[FLR_RING_SIZE];
83:
84: public static int flrTotal;
85:
86:
87:
88: public static boolean flrResetScreen;
89:
90: public static float[] flrBrightness = new float[4];
91:
92:
93: public static JCheckBoxMenuItem flrMenuEnabled;
94: public static JCheckBoxMenuItem flrMenuPalette;
95: public static JCheckBoxMenuItem flrMenuContrast;
96: public static JCheckBoxMenuItem flrMenuScreen;
97: public static DecimalSpinner flrMenuWavelength;
98: public static DecimalSpinner flrMenuLimit;
99: public static DecimalSpinner flrMenuDelay;
100:
101:
102:
103: public static void flrInit () {
104:
105: flrRequestEnabled = Settings.sgsGetOnOff ("flrenabled", FLR_ENABLED);
106: flrRequestPalette = Settings.sgsGetOnOff ("flrpalette", FLR_PALETTE);
107: flrContrast = Settings.sgsGetOnOff ("flrcontrast", FLR_CONTRAST);
108: flrRequestScreen = Settings.sgsGetOnOff ("flrscreen", FLR_SCREEN);
109: flrWavelength = Settings.sgsGetInt ("flrwavelength", FLR_WAVELENGTH, 1, 30);
110: flrLimit = Settings.sgsGetInt ("flrlimit", FLR_LIMIT, 0, 15);
111: flrDelay = Settings.sgsGetInt ("flrdelay", FLR_DELAY, 1, 60);
112: flrInformed = Settings.sgsGetOnOff ("flrinformed", FLR_INFORMED);
113: flrEnabled = false;
114: flrPalette = false;
115: flrScreen = false;
116: flrActive = flrEnabled || flrRequestEnabled;
117:
118: flrDebugLocation = false;
119:
120: flrTime = 0;
121:
122: Arrays.fill (flrGhostG8, 0);
123: Arrays.fill (flrGhostTS, 0);
124: Arrays.fill (flrHistoryG8, 0);
125: Arrays.fill (flrHistoryTS, 0);
126:
127: Arrays.fill (flrIndex, 0);
128: Arrays.fill (flrRing, 0);
129: flrTotal = 0;
130: flrResetScreen = false;
131: Arrays.fill (flrBrightness, 1.0F);
132:
133:
134: flrMakeIndex ();
135: }
136:
137:
138:
139: public static void flrInform () {
140: if (!flrInformed) {
141: int result = JOptionPane.showConfirmDialog (
142: XEiJ.frmFrame,
143: Multilingual.mlnJapanese ?
144: "光過敏性発作予防のための点滅光の軽減を有効にしますか?\n" +
145: "この機能を有効にすると、エミュレーションの正確さよりも安全性が優先されます。\n" +
146: "設定はアクセシビリティメニューから変更できます。" :
147: "Do you want to enable reducing flashing lights to prevent photosensitive seizures?\n" +
148: "Enabling this feature prioritizes safety over emulation accuracy.\n" +
149: "You can change the settings in the accessibility menu.",
150: Multilingual.mlnJapanese ? "点滅光の軽減" : "Flashing lights reduction",
151: JOptionPane.YES_NO_OPTION,
152: JOptionPane.PLAIN_MESSAGE);
153: if (result == JOptionPane.YES_OPTION) {
154: flrInitialize (true);
155: flrInformed = true;
156: } else if (result == JOptionPane.NO_OPTION) {
157: flrInitialize (false);
158: flrInformed = true;
159: }
160: }
161: }
162:
163:
164:
165: public static void flrTini () {
166: Settings.sgsPutOnOff ("flrenabled", flrRequestEnabled);
167: Settings.sgsPutOnOff ("flrpalette", flrRequestPalette);
168: Settings.sgsPutOnOff ("flrcontrast", flrContrast);
169: Settings.sgsPutOnOff ("flrscreen", flrRequestScreen);
170: Settings.sgsPutInt ("flrwavelength", flrWavelength);
171: Settings.sgsPutInt ("flrlimit", flrLimit);
172: Settings.sgsPutInt ("flrdelay", flrDelay);
173: Settings.sgsPutOnOff ("flrinformed", flrInformed);
174: }
175:
176:
177:
178:
179:
180:
181: public static void flrMakeIndex () {
182: int w = XEiJ.pnlScreenWidth;
183: int h = XEiJ.pnlScreenHeight;
184: int r = 26389;
185: for (int n = 0; n < 256; n++) {
186: r = (char) (15625 * r + 1);
187: int o = r >> 8;
188: int x = (w >> 9) + (((((n & 15) << 4) | (o & 15)) * w) >> 8);
189: int y = (h >> 9) + ((((n & 240) | (o >> 4)) * h) >> 8);
190: flrIndex[n] = x + (y << 10);
191: }
192: flrResetScreen = true;
193: }
194:
195:
196:
197:
198:
199:
200:
201:
202:
203:
204:
205:
206:
207:
208:
209:
210:
211:
212:
213:
214:
215:
216:
217:
218:
219:
220:
221:
222:
223:
224: public static int flrRiskOfGRBI (int grbi) {
225: return (70000 * ((grbi >> 6) & 31) +
226: 60037 * ((grbi >> 11) & 31) +
227: 10001 * ((grbi >> 1) & 31) +
228: 70019 * (grbi & 1));
229: }
230:
231:
232:
233:
234:
235:
236:
237: public static void flrWriteByteG8 (int a, int d) {
238: int t = VideoController.vcnPal16G8Port[(a >> 1) & 255];
239: if ((a & 1) == 0) {
240: flrWriteWordG8 (a, (d << 8) | (t & 255));
241: } else {
242: flrWriteWordG8 (a - 1, (t & 65280) | (d & 255));
243: }
244: }
245: public static void flrWriteWordG8 (int a, int d) {
246: int n = (a >> 1) & 255;
247: int grbi = (char) d;
248: VideoController.vcnPal16G8Port[n] = grbi;
249: if ((n & 1) == 0) {
250: VideoController.vcnPal8G16LPort[n] = grbi >> 8;
251: VideoController.vcnPal8G16LPort[n + 1] = grbi & 255;
252: } else {
253: VideoController.vcnPal8G16HPort[n - 1] = grbi & 65280;
254: VideoController.vcnPal8G16HPort[n] = (grbi & 255) << 8;
255: }
256: if (flrPalette) {
257: int grbi1 = flrHistoryG8[4 * n];
258: if (grbi1 != grbi) {
259: int time1 = flrHistoryG8[4 * n + 1];
260: int grbi2 = flrHistoryG8[4 * n + 2];
261: int time2 = flrHistoryG8[4 * n + 3];
262: int grbiOut = grbi;
263: if (grbi2 == grbi &&
264: (flrTime - time2) <= flrWavelength &&
265: flrRiskOfGRBI (grbi1) < flrRiskOfGRBI (grbi)) {
266: grbiOut = grbi1;
267: }
268: flrHistoryG8[4 * n] = grbi;
269: flrHistoryG8[4 * n + 1] = flrTime;
270: flrHistoryG8[4 * n + 2] = grbi1;
271: flrHistoryG8[4 * n + 3] = time1;
272: VideoController.vcnPal16G8[n] = grbiOut;
273: if ((n & 1) == 0) {
274: VideoController.vcnPal8G16L[n] = grbiOut >> 8;
275: VideoController.vcnPal8G16L[n + 1] = grbiOut & 255;
276: } else {
277: VideoController.vcnPal8G16H[n - 1] = grbiOut & 65280;
278: VideoController.vcnPal8G16H[n] = (grbiOut & 255) << 8;
279: }
280: }
281: }
282: VideoController.vcnPal32G8[n] = VideoController.vcnPalTbl[VideoController.vcnPal16G8[n]];
283: if ((VideoController.vcnReg3Curr & 0x001f) != 0) {
284: CRTC.crtAllStamp += 2;
285: }
286: }
287:
288:
289:
290:
291:
292:
293:
294: public static void flrWriteByteTS (int a, int d) {
295: int t = VideoController.vcnPal16G8Port[(a >> 1) & 255];
296: if ((a & 1) == 0) {
297: flrWriteWordTS (a, (d << 8) | (t & 255));
298: } else {
299: flrWriteWordTS (a - 1, (t & 65280) | (d & 255));
300: }
301: }
302: public static void flrWriteWordTS (int a, int d) {
303: int n = (a >> 1) & 255;
304: int grbi = (char) d;
305: VideoController.vcnPal16TSPort[n] = grbi;
306: if (flrPalette) {
307: int grbi1 = flrHistoryTS[4 * n];
308: if (grbi1 != grbi) {
309: int time1 = flrHistoryTS[4 * n + 1];
310: int grbi2 = flrHistoryTS[4 * n + 2];
311: int time2 = flrHistoryTS[4 * n + 3];
312: int grbiOut = grbi;
313: if (grbi2 == grbi &&
314: (flrTime - time2) <= flrWavelength &&
315: flrRiskOfGRBI (grbi1) < flrRiskOfGRBI (grbi)) {
316: grbiOut = grbi1;
317: }
318: flrHistoryTS[4 * n] = grbi;
319: flrHistoryTS[4 * n + 1] = flrTime;
320: flrHistoryTS[4 * n + 2] = grbi1;
321: flrHistoryTS[4 * n + 3] = time1;
322: VideoController.vcnPal16TS[n] = grbiOut;
323: }
324: }
325: VideoController.vcnPal32TS[n] = VideoController.vcnPalTbl[VideoController.vcnPal16TS[n]];
326: if (n < 16 ?
327: (VideoController.vcnReg3Curr & 0x0020) != 0 :
328: (VideoController.vcnReg3Curr & 0x0040) != 0 &&
329: (SpriteScreen.sprReg4BgCtrlCurr & 0x0200) != 0) {
330: CRTC.crtAllStamp += 2;
331: }
332: }
333:
334:
335:
336:
337:
338:
339:
340:
341:
342: public static void flrUpdateContrast () {
343: int curr = (VideoController.vcnTargetContrastMask != 0 ?
344: VideoController.vcnTargetContrastTest :
345: flrEnabled && flrContrast ?
346: Math.min (flrLimit, VideoController.vcnTargetContrastPort) :
347: VideoController.vcnTargetContrastPort);
348: if (VideoController.vcnTargetContrastCurr != curr) {
349: VideoController.vcnTargetContrastCurr = curr;
350: VideoController.vcnTargetScaledContrast = VideoController.VCN_CONTRAST_SCALE * VideoController.vcnTargetContrastCurr;
351: CRTC.crtContrastClock = XEiJ.mpuClockTime;
352: CRTC.crtFrameTaskClock = Math.min (CRTC.crtContrastClock, CRTC.crtCaptureClock);
353: }
354: }
355:
356:
357:
358:
359: public static void flrVsync () {
360:
361: flrTime++;
362:
363: if (flrPalette != (flrRequestEnabled && flrRequestPalette)) {
364: flrPalette = (flrRequestEnabled && flrRequestPalette);
365:
366: if (flrPalette) {
367:
368: System.arraycopy (VideoController.vcnPal16G8Port, 0,
369: flrGhostG8, 0,
370: 256);
371:
372: VideoController.vcnPal16G8 = flrGhostG8;
373: } else {
374:
375: VideoController.vcnPal16G8 = VideoController.vcnPal16G8Port;
376: }
377: for (int n = 0; n < 256; n++) {
378: int grbi = VideoController.vcnPal16G8[n];
379: VideoController.vcnPal32G8[n] = VideoController.vcnPalTbl[grbi];
380: if ((n & 1) == 0) {
381: VideoController.vcnPal8G16L[n] = grbi >> 8;
382: VideoController.vcnPal8G16L[n + 1] = grbi & 255;
383: } else {
384: VideoController.vcnPal8G16H[n - 1] = grbi & 65280;
385: VideoController.vcnPal8G16H[n] = (grbi & 255) << 8;
386: }
387: }
388:
389: if (flrPalette) {
390:
391: System.arraycopy (VideoController.vcnPal16TSPort, 0,
392: flrGhostTS, 0,
393: 256);
394: if (!ScreenModeTest.smtPatternTestOn) {
395:
396: VideoController.vcnPal16TS = flrGhostTS;
397: }
398: } else {
399:
400: if (!ScreenModeTest.smtPatternTestOn) {
401: VideoController.vcnPal16TS = VideoController.vcnPal16TSPort;
402: }
403: }
404: for (int n = 0; n < 256; n++) {
405: VideoController.vcnPal32TS[n] = VideoController.vcnPalTbl[VideoController.vcnPal16TS[n]];
406: }
407: if ((VideoController.vcnReg3Curr & 0x007f) != 0) {
408: CRTC.crtAllStamp += 2;
409: }
410:
411: Arrays.fill (flrHistoryG8, 0);
412: Arrays.fill (flrHistoryTS, 0);
413: }
414:
415: if (flrScreen != (flrRequestEnabled && flrRequestScreen)) {
416: flrScreen = (flrRequestEnabled && flrRequestScreen);
417: if (flrScreen) {
418: flrResetScreen = true;
419: }
420: }
421:
422: if (flrEnabled != flrRequestEnabled) {
423: flrEnabled = flrRequestEnabled;
424: flrActive = flrEnabled || flrRequestEnabled;
425: flrUpdateContrast ();
426: }
427: if (!flrEnabled) {
428: return;
429: }
430:
431: if (flrResetScreen) {
432: flrResetScreen = false;
433: int sum = 0;
434: for (int n = 0; n < 256; n++) {
435: int argb = XEiJ.pnlBM[flrIndex[n]];
436: sum += (7 * (((argb >> 16) & 255)) +
437: 6 * ((argb >> 8) & 255) +
438: (argb & 255));
439: }
440: Arrays.fill (flrRing, sum);
441: flrTotal = sum * flrDelay;
442: }
443:
444: if (flrScreen) {
445:
446: int sum = 0;
447: for (int n = 0; n < 256; n++) {
448: int argb = XEiJ.pnlBM[flrIndex[n]];
449: if (FLR_DEBUG &&
450: flrDebugLocation) {
451: XEiJ.pnlBM[flrIndex[n]] = 0xff00ff00;
452: }
453: sum += (7 * (((argb >> 16) & 255)) +
454: 6 * ((argb >> 8) & 255) +
455: (argb & 255));
456: }
457:
458: flrTotal += sum - flrRing[(flrTime - flrDelay) & (FLR_RING_SIZE - 1)];
459:
460: flrRing[flrTime & (FLR_RING_SIZE - 1)] = sum;
461:
462: float average = (float) flrTotal / (float) flrDelay;
463:
464: flrBrightness[XEiJ.pnlBMWrite & 3] = (float) sum <= average ? 1.0F : average / (float) sum;
465: }
466: }
467:
468:
469:
470: public static JMenu flrMakeMenu () {
471: return Multilingual.mlnText (
472: ComponentFactory.createMenu (
473: "Flashing lights reduction",
474:
475: flrMenuEnabled = Multilingual.mlnText (
476: ComponentFactory.createCheckBoxMenuItem (
477: flrRequestEnabled, "Reduce flashing lights",
478: new ActionListener () {
479: @Override public void actionPerformed (ActionEvent ae) {
480: flrRequestEnabled = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
481: flrActive = flrEnabled || flrRequestEnabled;
482: }}),
483: "ja", "点滅光を軽減する"),
484:
485: flrMenuPalette = Multilingual.mlnText (
486: ComponentFactory.createCheckBoxMenuItem (
487: flrRequestPalette, "Stop flashing with palette",
488: new ActionListener () {
489: @Override public void actionPerformed (ActionEvent ae) {
490: flrRequestPalette = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
491: }}),
492: "ja", "パレットによる点滅を止める"),
493:
494: flrMenuContrast = Multilingual.mlnText (
495: ComponentFactory.createCheckBoxMenuItem (
496: flrContrast, "Limit contrast",
497: new ActionListener () {
498: @Override public void actionPerformed (ActionEvent ae) {
499: flrContrast = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
500: flrUpdateContrast ();
501: }}),
502: "ja", "コントラストを制限する"),
503:
504: Multilingual.mlnText (
505: flrMenuScreen = ComponentFactory.createCheckBoxMenuItem (
506: flrRequestScreen, "Brighten screen slowly",
507: new ActionListener () {
508: @Override public void actionPerformed (ActionEvent ae) {
509: flrRequestScreen = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
510: }}),
511: "ja", "画面を徐々に明るくする"),
512:
513: ComponentFactory.createHorizontalBox (
514: Box.createHorizontalStrut (20),
515: flrMenuWavelength = ComponentFactory.createDecimalSpinner (
516: flrWavelength, 1, 30, 1, 0,
517: new ChangeListener () {
518: @Override public void stateChanged (ChangeEvent ce) {
519: flrWavelength = ((DecimalSpinner) ce.getSource ()).getIntValue ();
520: }}),
521: Multilingual.mlnText (
522: ComponentFactory.createLabel (" Wavelength of flashing with palette"),
523: "ja", "パレットによる点滅の波長"),
524: Box.createHorizontalGlue ()),
525:
526: ComponentFactory.createHorizontalBox (
527: Box.createHorizontalStrut (20),
528: flrMenuLimit = ComponentFactory.createDecimalSpinner (
529: flrLimit, 0, 15, 1, 0,
530: new ChangeListener () {
531: @Override public void stateChanged (ChangeEvent ce) {
532: flrLimit = ((DecimalSpinner) ce.getSource ()).getIntValue ();
533: flrUpdateContrast ();
534: }}),
535: Multilingual.mlnText (
536: ComponentFactory.createLabel (" Upper limit of contrast"),
537: "ja", "コントラストの上限"),
538: Box.createHorizontalGlue ()),
539:
540: ComponentFactory.createHorizontalBox (
541: Box.createHorizontalStrut (20),
542: flrMenuDelay = ComponentFactory.createDecimalSpinner (
543: flrDelay, 1, 60, 1, 0,
544: new ChangeListener () {
545: @Override public void stateChanged (ChangeEvent ce) {
546: flrDelay = ((DecimalSpinner) ce.getSource ()).getIntValue ();
547: flrResetScreen = true;
548: }}),
549: Multilingual.mlnText (
550: ComponentFactory.createLabel (" Time until the screen brighten"),
551: "ja", " 画面が明るくなるまでの時間"),
552: Box.createHorizontalGlue ()),
553:
554: ComponentFactory.createHorizontalSeparator (),
555: Multilingual.mlnText (
556: ComponentFactory.createMenuItem (
557: "Reset to default",
558: new ActionListener () {
559: @Override public void actionPerformed (ActionEvent ae) {
560: flrResetToDefault ();
561: }}),
562: "ja", "初期値に戻す"),
563:
564: !FLR_DEBUG ? null :
565: ComponentFactory.createHorizontalSeparator (),
566: !FLR_DEBUG ? null :
567: Multilingual.mlnText (
568: ComponentFactory.createMenu (
569: "Debug",
570: Multilingual.mlnText (
571: ComponentFactory.createCheckBoxMenuItem (
572: flrDebugLocation, "Display locations",
573: new ActionListener () {
574: @Override public void actionPerformed (ActionEvent ae) {
575: flrDebugLocation = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
576: }}),
577: "ja", "観測点を表示する")
578: ),
579: "ja", "デバッグ")
580: ),
581: "ja", "点滅光の軽減");
582: }
583:
584:
585:
586: public static void flrResetToDefault () {
587: int result = JOptionPane.showConfirmDialog (
588: XEiJ.frmFrame,
589: Multilingual.mlnJapanese ?
590: "点滅光の軽減の設定を初期値に戻しますか?\n" +
591: "この機能は一旦無効になります。":
592: "Do you want to reset the flashing lights reduction settings to default?" +
593: "This feature will be temporarily disabled.",
594: Multilingual.mlnJapanese ? "点滅光の軽減" : "Flashing lights reduction",
595: JOptionPane.YES_NO_OPTION,
596: JOptionPane.PLAIN_MESSAGE);
597: if (result == JOptionPane.YES_OPTION) {
598: flrInitialize (FLR_ENABLED);
599: }
600: }
601:
602:
603:
604: public static void flrInitialize (boolean enabled) {
605: flrMenuEnabled.setSelected (flrRequestEnabled = enabled);
606: flrActive = flrEnabled || flrRequestEnabled;
607: flrMenuPalette.setSelected (flrRequestPalette = FLR_PALETTE);
608: flrMenuContrast.setSelected (flrContrast = FLR_CONTRAST);
609: flrMenuScreen.setSelected (flrRequestScreen = FLR_SCREEN);
610: flrMenuWavelength.setIntValue (flrWavelength = FLR_WAVELENGTH);
611: flrMenuLimit.setIntValue (flrLimit = FLR_LIMIT);
612: flrUpdateContrast ();
613: flrMenuDelay.setIntValue (flrDelay = FLR_DELAY);
614: flrInformed = false;
615: }
616:
617: }