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 (flrPalette) {
250: int grbi1 = flrHistoryG8[4 * n];
251: if (grbi1 != grbi) {
252: int time1 = flrHistoryG8[4 * n + 1];
253: int grbi2 = flrHistoryG8[4 * n + 2];
254: int time2 = flrHistoryG8[4 * n + 3];
255: int grbiOut = grbi;
256: if (grbi2 == grbi &&
257: (flrTime - time2) <= flrWavelength &&
258: flrRiskOfGRBI (grbi1) < flrRiskOfGRBI (grbi)) {
259: grbiOut = grbi1;
260: }
261: flrHistoryG8[4 * n] = grbi;
262: flrHistoryG8[4 * n + 1] = flrTime;
263: flrHistoryG8[4 * n + 2] = grbi1;
264: flrHistoryG8[4 * n + 3] = time1;
265: VideoController.vcnPal16G8[n] = grbiOut;
266: if ((n & 1) == 0) {
267: VideoController.vcnPal8G16L[n] = grbiOut >> 8;
268: VideoController.vcnPal8G16L[n + 1] = grbiOut & 255;
269: } else {
270: VideoController.vcnPal8G16H[n - 1] = grbiOut & 65280;
271: VideoController.vcnPal8G16H[n] = (grbiOut & 255) << 8;
272: }
273: }
274: }
275: VideoController.vcnPal32G8[n] = VideoController.vcnPalTbl[VideoController.vcnPal16G8[n]];
276: if ((VideoController.vcnReg3Curr & 0x001f) != 0) {
277: CRTC.crtAllStamp += 2;
278: }
279: }
280:
281:
282:
283:
284:
285:
286:
287: public static void flrWriteByteTS (int a, int d) {
288: int t = VideoController.vcnPal16G8Port[(a >> 1) & 255];
289: if ((a & 1) == 0) {
290: flrWriteWordTS (a, (d << 8) | (t & 255));
291: } else {
292: flrWriteWordTS (a - 1, (t & 65280) | (d & 255));
293: }
294: }
295: public static void flrWriteWordTS (int a, int d) {
296: int n = (a >> 1) & 255;
297: int grbi = (char) d;
298: VideoController.vcnPal16TSPort[n] = grbi;
299: if (flrPalette) {
300: int grbi1 = flrHistoryTS[4 * n];
301: if (grbi1 != grbi) {
302: int time1 = flrHistoryTS[4 * n + 1];
303: int grbi2 = flrHistoryTS[4 * n + 2];
304: int time2 = flrHistoryTS[4 * n + 3];
305: int grbiOut = grbi;
306: if (grbi2 == grbi &&
307: (flrTime - time2) <= flrWavelength &&
308: flrRiskOfGRBI (grbi1) < flrRiskOfGRBI (grbi)) {
309: grbiOut = grbi1;
310: }
311: flrHistoryTS[4 * n] = grbi;
312: flrHistoryTS[4 * n + 1] = flrTime;
313: flrHistoryTS[4 * n + 2] = grbi1;
314: flrHistoryTS[4 * n + 3] = time1;
315: VideoController.vcnPal16TS[n] = grbiOut;
316: }
317: }
318: VideoController.vcnPal32TS[n] = VideoController.vcnPalTbl[VideoController.vcnPal16TS[n]];
319: if (n < 16 ?
320: (VideoController.vcnReg3Curr & 0x0020) != 0 :
321: (VideoController.vcnReg3Curr & 0x0040) != 0 &&
322: (SpriteScreen.sprReg4BgCtrlCurr & 0x0200) != 0) {
323: CRTC.crtAllStamp += 2;
324: }
325: }
326:
327:
328:
329:
330:
331:
332:
333:
334:
335: public static void flrUpdateContrast () {
336: int curr = (VideoController.vcnTargetContrastMask != 0 ?
337: VideoController.vcnTargetContrastTest :
338: flrEnabled && flrContrast ?
339: Math.min (flrLimit, VideoController.vcnTargetContrastPort) :
340: VideoController.vcnTargetContrastPort);
341: if (VideoController.vcnTargetContrastCurr != curr) {
342: VideoController.vcnTargetContrastCurr = curr;
343: VideoController.vcnTargetScaledContrast = VideoController.VCN_CONTRAST_SCALE * VideoController.vcnTargetContrastCurr;
344: CRTC.crtContrastClock = XEiJ.mpuClockTime;
345: CRTC.crtFrameTaskClock = Math.min (CRTC.crtContrastClock, CRTC.crtCaptureClock);
346: }
347: }
348:
349:
350:
351:
352: public static void flrVsync () {
353:
354: flrTime++;
355:
356: if (flrPalette != (flrRequestEnabled && flrRequestPalette)) {
357: flrPalette = (flrRequestEnabled && flrRequestPalette);
358:
359: if (flrPalette) {
360:
361: System.arraycopy (VideoController.vcnPal16G8Port, 0,
362: flrGhostG8, 0,
363: 256);
364:
365: VideoController.vcnPal16G8 = flrGhostG8;
366: } else {
367:
368: VideoController.vcnPal16G8 = VideoController.vcnPal16G8Port;
369: }
370: for (int n = 0; n < 256; n++) {
371: int grbi = VideoController.vcnPal16G8[n];
372: VideoController.vcnPal32G8[n] = VideoController.vcnPalTbl[grbi];
373: if ((n & 1) == 0) {
374: VideoController.vcnPal8G16L[n] = grbi >> 8;
375: VideoController.vcnPal8G16L[n + 1] = grbi & 255;
376: } else {
377: VideoController.vcnPal8G16H[n - 1] = grbi & 65280;
378: VideoController.vcnPal8G16H[n] = (grbi & 255) << 8;
379: }
380: }
381:
382: if (flrPalette) {
383:
384: System.arraycopy (VideoController.vcnPal16TSPort, 0,
385: flrGhostTS, 0,
386: 256);
387: if (!ScreenModeTest.smtPatternTestOn) {
388:
389: VideoController.vcnPal16TS = flrGhostTS;
390: }
391: } else {
392:
393: if (!ScreenModeTest.smtPatternTestOn) {
394: VideoController.vcnPal16TS = VideoController.vcnPal16TSPort;
395: }
396: }
397: for (int n = 0; n < 256; n++) {
398: VideoController.vcnPal32TS[n] = VideoController.vcnPalTbl[VideoController.vcnPal16TS[n]];
399: }
400: if ((VideoController.vcnReg3Curr & 0x007f) != 0) {
401: CRTC.crtAllStamp += 2;
402: }
403:
404: Arrays.fill (flrHistoryG8, 0);
405: Arrays.fill (flrHistoryTS, 0);
406: }
407:
408: if (flrScreen != (flrRequestEnabled && flrRequestScreen)) {
409: flrScreen = (flrRequestEnabled && flrRequestScreen);
410: if (flrScreen) {
411: flrResetScreen = true;
412: }
413: }
414:
415: if (flrEnabled != flrRequestEnabled) {
416: flrEnabled = flrRequestEnabled;
417: flrActive = flrEnabled || flrRequestEnabled;
418: flrUpdateContrast ();
419: }
420: if (!flrEnabled) {
421: return;
422: }
423:
424: if (flrResetScreen) {
425: flrResetScreen = false;
426: int sum = 0;
427: for (int n = 0; n < 256; n++) {
428: int argb = XEiJ.pnlBM[flrIndex[n]];
429: sum += (7 * (((argb >> 16) & 255)) +
430: 6 * ((argb >> 8) & 255) +
431: (argb & 255));
432: }
433: Arrays.fill (flrRing, sum);
434: flrTotal = sum * flrDelay;
435: }
436:
437: if (flrScreen) {
438:
439: int sum = 0;
440: for (int n = 0; n < 256; n++) {
441: int argb = XEiJ.pnlBM[flrIndex[n]];
442: if (FLR_DEBUG &&
443: flrDebugLocation) {
444: XEiJ.pnlBM[flrIndex[n]] = 0xff00ff00;
445: }
446: sum += (7 * (((argb >> 16) & 255)) +
447: 6 * ((argb >> 8) & 255) +
448: (argb & 255));
449: }
450:
451: flrTotal += sum - flrRing[(flrTime - flrDelay) & (FLR_RING_SIZE - 1)];
452:
453: flrRing[flrTime & (FLR_RING_SIZE - 1)] = sum;
454:
455: float average = (float) flrTotal / (float) flrDelay;
456:
457: flrBrightness[XEiJ.pnlBMWrite & 3] = (float) sum <= average ? 1.0F : average / (float) sum;
458: }
459: }
460:
461:
462:
463: public static JMenu flrMakeMenu () {
464: return Multilingual.mlnText (
465: ComponentFactory.createMenu (
466: "Flashing lights reduction",
467:
468: flrMenuEnabled = Multilingual.mlnText (
469: ComponentFactory.createCheckBoxMenuItem (
470: flrRequestEnabled, "Reduce flashing lights",
471: new ActionListener () {
472: @Override public void actionPerformed (ActionEvent ae) {
473: flrRequestEnabled = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
474: flrActive = flrEnabled || flrRequestEnabled;
475: }}),
476: "ja", "点滅光を軽減する"),
477:
478: flrMenuPalette = Multilingual.mlnText (
479: ComponentFactory.createCheckBoxMenuItem (
480: flrRequestPalette, "Stop flashing with palette",
481: new ActionListener () {
482: @Override public void actionPerformed (ActionEvent ae) {
483: flrRequestPalette = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
484: }}),
485: "ja", "パレットによる点滅を止める"),
486:
487: flrMenuContrast = Multilingual.mlnText (
488: ComponentFactory.createCheckBoxMenuItem (
489: flrContrast, "Limit contrast",
490: new ActionListener () {
491: @Override public void actionPerformed (ActionEvent ae) {
492: flrContrast = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
493: flrUpdateContrast ();
494: }}),
495: "ja", "コントラストを制限する"),
496:
497: Multilingual.mlnText (
498: flrMenuScreen = ComponentFactory.createCheckBoxMenuItem (
499: flrRequestScreen, "Brighten screen slowly",
500: new ActionListener () {
501: @Override public void actionPerformed (ActionEvent ae) {
502: flrRequestScreen = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
503: }}),
504: "ja", "画面を徐々に明るくする"),
505:
506: ComponentFactory.createHorizontalBox (
507: Box.createHorizontalStrut (20),
508: flrMenuWavelength = ComponentFactory.createDecimalSpinner (
509: flrWavelength, 1, 30, 1, 0,
510: new ChangeListener () {
511: @Override public void stateChanged (ChangeEvent ce) {
512: flrWavelength = ((DecimalSpinner) ce.getSource ()).getIntValue ();
513: }}),
514: Multilingual.mlnText (
515: ComponentFactory.createLabel (" Wavelength of flashing with palette"),
516: "ja", "パレットによる点滅の波長"),
517: Box.createHorizontalGlue ()),
518:
519: ComponentFactory.createHorizontalBox (
520: Box.createHorizontalStrut (20),
521: flrMenuLimit = ComponentFactory.createDecimalSpinner (
522: flrLimit, 0, 15, 1, 0,
523: new ChangeListener () {
524: @Override public void stateChanged (ChangeEvent ce) {
525: flrLimit = ((DecimalSpinner) ce.getSource ()).getIntValue ();
526: flrUpdateContrast ();
527: }}),
528: Multilingual.mlnText (
529: ComponentFactory.createLabel (" Upper limit of contrast"),
530: "ja", "コントラストの上限"),
531: Box.createHorizontalGlue ()),
532:
533: ComponentFactory.createHorizontalBox (
534: Box.createHorizontalStrut (20),
535: flrMenuDelay = ComponentFactory.createDecimalSpinner (
536: flrDelay, 1, 60, 1, 0,
537: new ChangeListener () {
538: @Override public void stateChanged (ChangeEvent ce) {
539: flrDelay = ((DecimalSpinner) ce.getSource ()).getIntValue ();
540: flrResetScreen = true;
541: }}),
542: Multilingual.mlnText (
543: ComponentFactory.createLabel (" Time until the screen brighten"),
544: "ja", " 画面が明るくなるまでの時間"),
545: Box.createHorizontalGlue ()),
546:
547: ComponentFactory.createHorizontalSeparator (),
548: Multilingual.mlnText (
549: ComponentFactory.createMenuItem (
550: "Reset to default",
551: new ActionListener () {
552: @Override public void actionPerformed (ActionEvent ae) {
553: flrResetToDefault ();
554: }}),
555: "ja", "初期値に戻す"),
556:
557: !FLR_DEBUG ? null :
558: ComponentFactory.createHorizontalSeparator (),
559: !FLR_DEBUG ? null :
560: Multilingual.mlnText (
561: ComponentFactory.createMenu (
562: "Debug",
563: Multilingual.mlnText (
564: ComponentFactory.createCheckBoxMenuItem (
565: flrDebugLocation, "Display locations",
566: new ActionListener () {
567: @Override public void actionPerformed (ActionEvent ae) {
568: flrDebugLocation = ((JCheckBoxMenuItem) ae.getSource ()).isSelected ();
569: }}),
570: "ja", "観測点を表示する")
571: ),
572: "ja", "デバッグ")
573: ),
574: "ja", "点滅光の軽減");
575: }
576:
577:
578:
579: public static void flrResetToDefault () {
580: int result = JOptionPane.showConfirmDialog (
581: XEiJ.frmFrame,
582: Multilingual.mlnJapanese ?
583: "点滅光の軽減の設定を初期値に戻しますか?\n" +
584: "この機能は一旦無効になります。":
585: "Do you want to reset the flashing lights reduction settings to default?" +
586: "This feature will be temporarily disabled.",
587: Multilingual.mlnJapanese ? "点滅光の軽減" : "Flashing lights reduction",
588: JOptionPane.YES_NO_OPTION,
589: JOptionPane.PLAIN_MESSAGE);
590: if (result == JOptionPane.YES_OPTION) {
591: flrInitialize (FLR_ENABLED);
592: }
593: }
594:
595:
596:
597: public static void flrInitialize (boolean enabled) {
598: flrMenuEnabled.setSelected (flrRequestEnabled = enabled);
599: flrActive = flrEnabled || flrRequestEnabled;
600: flrMenuPalette.setSelected (flrRequestPalette = FLR_PALETTE);
601: flrMenuContrast.setSelected (flrContrast = FLR_CONTRAST);
602: flrMenuScreen.setSelected (flrRequestScreen = FLR_SCREEN);
603: flrMenuWavelength.setIntValue (flrWavelength = FLR_WAVELENGTH);
604: flrMenuLimit.setIntValue (flrLimit = FLR_LIMIT);
605: flrUpdateContrast ();
606: flrMenuDelay.setIntValue (flrDelay = FLR_DELAY);
607: flrInformed = false;
608: }
609:
610: }