The response has been limited to 50k tokens of the smallest files in the repo. You can remove this limitation by removing the max tokens filter.
├── .eslintrc.json
├── .github
    ├── dependabot.yml
    └── workflows
    │   └── ci.yaml
├── .gitignore
├── .npmignore
├── AUTHORS.md
├── LICENSE
├── README.md
├── example
    ├── InterglacticTransmissing.nes
    ├── README.md
    ├── nes-embed.html
    └── nes-embed.js
├── package.json
├── roms
    ├── croom
    │   ├── README.html
    │   └── croom.nes
    └── lj65
    │   ├── README.txt
    │   └── lj65.nes
├── src
    ├── controller.js
    ├── cpu.js
    ├── index.js
    ├── mappers.js
    ├── nes.js
    ├── papu.js
    ├── ppu.js
    ├── rom.js
    ├── tile.js
    └── utils.js
├── test
    └── nes.spec.js
├── webpack.config.js
└── yarn.lock


/.eslintrc.json:
--------------------------------------------------------------------------------
 1 | {
 2 |     "env": {
 3 |         "browser": true,
 4 |         "node": true,
 5 |         "es6": true,
 6 |         "commonjs": true
 7 |     },
 8 |     "extends": ["eslint:recommended", "prettier"],
 9 |     "rules": {
10 |       "eqeqeq": ["error", "always"],
11 |       "no-alert": "error"
12 |     }
13 | }
14 | 


--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
 1 | version: 2
 2 | updates:
 3 | - package-ecosystem: npm
 4 |   directory: "/"
 5 |   schedule:
 6 |     interval: weekly
 7 |   open-pull-requests-limit: 10
 8 |   ignore:
 9 |   - dependency-name: webpack
10 |     versions:
11 |     - "< 5"
12 |     - ">= 4.0.a"
13 | 


--------------------------------------------------------------------------------
/.github/workflows/ci.yaml:
--------------------------------------------------------------------------------
 1 | name: CI
 2 | 
 3 | on: [push, pull_request]
 4 | 
 5 | jobs:
 6 |   build:
 7 | 
 8 |     runs-on: ubuntu-latest
 9 | 
10 |     steps:
11 |       - uses: actions/checkout@v2
12 |       - name: Use Node.js
13 |         uses: actions/setup-node@v2
14 |         with:
15 |           node-version: '12.x'
16 |       - run: yarn
17 |       - run: yarn build
18 |       - run: yarn test
19 | 


--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | ._*
2 | .DS_Store
3 | /dist
4 | /local-roms
5 | /node_modules
6 | /tmp
7 | 


--------------------------------------------------------------------------------
/.npmignore:
--------------------------------------------------------------------------------
1 | /local-roms
2 | /tmp
3 | 


--------------------------------------------------------------------------------
/AUTHORS.md:
--------------------------------------------------------------------------------
 1 | Authors
 2 | =======
 3 | 
 4 |  * Ben Firshman
 5 | 
 6 | Thanks to:
 7 | 
 8 |  * Jamie Sanders for vNES, the Java emulator that JSNES owes so much to.
 9 |  * Matt Westcott for JSSpeccy, the original inspiration for JSNES.
10 |  * Connor Dunn for a patch that dramatically increased performance on Chrome.
11 |  * Jens Lindstrom for some optimisations.
12 |  * Rafal Chlodnicki for an Opera fix.
13 |  * Ecin Krispie for fixing player 2 controls.
14 | 


--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
  1 | 
  2 |                                  Apache License
  3 |                            Version 2.0, January 2004
  4 |                         http://www.apache.org/licenses/
  5 | 
  6 |    TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
  7 | 
  8 |    1. Definitions.
  9 | 
 10 |       "License" shall mean the terms and conditions for use, reproduction,
 11 |       and distribution as defined by Sections 1 through 9 of this document.
 12 | 
 13 |       "Licensor" shall mean the copyright owner or entity authorized by
 14 |       the copyright owner that is granting the License.
 15 | 
 16 |       "Legal Entity" shall mean the union of the acting entity and all
 17 |       other entities that control, are controlled by, or are under common
 18 |       control with that entity. For the purposes of this definition,
 19 |       "control" means (i) the power, direct or indirect, to cause the
 20 |       direction or management of such entity, whether by contract or
 21 |       otherwise, or (ii) ownership of fifty percent (50%) or more of the
 22 |       outstanding shares, or (iii) beneficial ownership of such entity.
 23 | 
 24 |       "You" (or "Your") shall mean an individual or Legal Entity
 25 |       exercising permissions granted by this License.
 26 | 
 27 |       "Source" form shall mean the preferred form for making modifications,
 28 |       including but not limited to software source code, documentation
 29 |       source, and configuration files.
 30 | 
 31 |       "Object" form shall mean any form resulting from mechanical
 32 |       transformation or translation of a Source form, including but
 33 |       not limited to compiled object code, generated documentation,
 34 |       and conversions to other media types.
 35 | 
 36 |       "Work" shall mean the work of authorship, whether in Source or
 37 |       Object form, made available under the License, as indicated by a
 38 |       copyright notice that is included in or attached to the work
 39 |       (an example is provided in the Appendix below).
 40 | 
 41 |       "Derivative Works" shall mean any work, whether in Source or Object
 42 |       form, that is based on (or derived from) the Work and for which the
 43 |       editorial revisions, annotations, elaborations, or other modifications
 44 |       represent, as a whole, an original work of authorship. For the purposes
 45 |       of this License, Derivative Works shall not include works that remain
 46 |       separable from, or merely link (or bind by name) to the interfaces of,
 47 |       the Work and Derivative Works thereof.
 48 | 
 49 |       "Contribution" shall mean any work of authorship, including
 50 |       the original version of the Work and any modifications or additions
 51 |       to that Work or Derivative Works thereof, that is intentionally
 52 |       submitted to Licensor for inclusion in the Work by the copyright owner
 53 |       or by an individual or Legal Entity authorized to submit on behalf of
 54 |       the copyright owner. For the purposes of this definition, "submitted"
 55 |       means any form of electronic, verbal, or written communication sent
 56 |       to the Licensor or its representatives, including but not limited to
 57 |       communication on electronic mailing lists, source code control systems,
 58 |       and issue tracking systems that are managed by, or on behalf of, the
 59 |       Licensor for the purpose of discussing and improving the Work, but
 60 |       excluding communication that is conspicuously marked or otherwise
 61 |       designated in writing by the copyright owner as "Not a Contribution."
 62 | 
 63 |       "Contributor" shall mean Licensor and any individual or Legal Entity
 64 |       on behalf of whom a Contribution has been received by Licensor and
 65 |       subsequently incorporated within the Work.
 66 | 
 67 |    2. Grant of Copyright License. Subject to the terms and conditions of
 68 |       this License, each Contributor hereby grants to You a perpetual,
 69 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 70 |       copyright license to reproduce, prepare Derivative Works of,
 71 |       publicly display, publicly perform, sublicense, and distribute the
 72 |       Work and such Derivative Works in Source or Object form.
 73 | 
 74 |    3. Grant of Patent License. Subject to the terms and conditions of
 75 |       this License, each Contributor hereby grants to You a perpetual,
 76 |       worldwide, non-exclusive, no-charge, royalty-free, irrevocable
 77 |       (except as stated in this section) patent license to make, have made,
 78 |       use, offer to sell, sell, import, and otherwise transfer the Work,
 79 |       where such license applies only to those patent claims licensable
 80 |       by such Contributor that are necessarily infringed by their
 81 |       Contribution(s) alone or by combination of their Contribution(s)
 82 |       with the Work to which such Contribution(s) was submitted. If You
 83 |       institute patent litigation against any entity (including a
 84 |       cross-claim or counterclaim in a lawsuit) alleging that the Work
 85 |       or a Contribution incorporated within the Work constitutes direct
 86 |       or contributory patent infringement, then any patent licenses
 87 |       granted to You under this License for that Work shall terminate
 88 |       as of the date such litigation is filed.
 89 | 
 90 |    4. Redistribution. You may reproduce and distribute copies of the
 91 |       Work or Derivative Works thereof in any medium, with or without
 92 |       modifications, and in Source or Object form, provided that You
 93 |       meet the following conditions:
 94 | 
 95 |       (a) You must give any other recipients of the Work or
 96 |           Derivative Works a copy of this License; and
 97 | 
 98 |       (b) You must cause any modified files to carry prominent notices
 99 |           stating that You changed the files; and
100 | 
101 |       (c) You must retain, in the Source form of any Derivative Works
102 |           that You distribute, all copyright, patent, trademark, and
103 |           attribution notices from the Source form of the Work,
104 |           excluding those notices that do not pertain to any part of
105 |           the Derivative Works; and
106 | 
107 |       (d) If the Work includes a "NOTICE" text file as part of its
108 |           distribution, then any Derivative Works that You distribute must
109 |           include a readable copy of the attribution notices contained
110 |           within such NOTICE file, excluding those notices that do not
111 |           pertain to any part of the Derivative Works, in at least one
112 |           of the following places: within a NOTICE text file distributed
113 |           as part of the Derivative Works; within the Source form or
114 |           documentation, if provided along with the Derivative Works; or,
115 |           within a display generated by the Derivative Works, if and
116 |           wherever such third-party notices normally appear. The contents
117 |           of the NOTICE file are for informational purposes only and
118 |           do not modify the License. You may add Your own attribution
119 |           notices within Derivative Works that You distribute, alongside
120 |           or as an addendum to the NOTICE text from the Work, provided
121 |           that such additional attribution notices cannot be construed
122 |           as modifying the License.
123 | 
124 |       You may add Your own copyright statement to Your modifications and
125 |       may provide additional or different license terms and conditions
126 |       for use, reproduction, or distribution of Your modifications, or
127 |       for any such Derivative Works as a whole, provided Your use,
128 |       reproduction, and distribution of the Work otherwise complies with
129 |       the conditions stated in this License.
130 | 
131 |    5. Submission of Contributions. Unless You explicitly state otherwise,
132 |       any Contribution intentionally submitted for inclusion in the Work
133 |       by You to the Licensor shall be under the terms and conditions of
134 |       this License, without any additional terms or conditions.
135 |       Notwithstanding the above, nothing herein shall supersede or modify
136 |       the terms of any separate license agreement you may have executed
137 |       with Licensor regarding such Contributions.
138 | 
139 |    6. Trademarks. This License does not grant permission to use the trade
140 |       names, trademarks, service marks, or product names of the Licensor,
141 |       except as required for reasonable and customary use in describing the
142 |       origin of the Work and reproducing the content of the NOTICE file.
143 | 
144 |    7. Disclaimer of Warranty. Unless required by applicable law or
145 |       agreed to in writing, Licensor provides the Work (and each
146 |       Contributor provides its Contributions) on an "AS IS" BASIS,
147 |       WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 |       implied, including, without limitation, any warranties or conditions
149 |       of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 |       PARTICULAR PURPOSE. You are solely responsible for determining the
151 |       appropriateness of using or redistributing the Work and assume any
152 |       risks associated with Your exercise of permissions under this License.
153 | 
154 |    8. Limitation of Liability. In no event and under no legal theory,
155 |       whether in tort (including negligence), contract, or otherwise,
156 |       unless required by applicable law (such as deliberate and grossly
157 |       negligent acts) or agreed to in writing, shall any Contributor be
158 |       liable to You for damages, including any direct, indirect, special,
159 |       incidental, or consequential damages of any character arising as a
160 |       result of this License or out of the use or inability to use the
161 |       Work (including but not limited to damages for loss of goodwill,
162 |       work stoppage, computer failure or malfunction, or any and all
163 |       other commercial damages or losses), even if such Contributor
164 |       has been advised of the possibility of such damages.
165 | 
166 |    9. Accepting Warranty or Additional Liability. While redistributing
167 |       the Work or Derivative Works thereof, You may choose to offer,
168 |       and charge a fee for, acceptance of support, warranty, indemnity,
169 |       or other liability obligations and/or rights consistent with this
170 |       License. However, in accepting such obligations, You may act only
171 |       on Your own behalf and on Your sole responsibility, not on behalf
172 |       of any other Contributor, and only if You agree to indemnify,
173 |       defend, and hold each Contributor harmless for any liability
174 |       incurred by, or claims asserted against, such Contributor by reason
175 |       of your accepting any such warranty or additional liability.
176 | 
177 |    END OF TERMS AND CONDITIONS
178 | 
179 |    Copyright 2020 Ben Firshman
180 | 
181 |    Licensed under the Apache License, Version 2.0 (the "License");
182 |    you may not use this file except in compliance with the License.
183 |    You may obtain a copy of the License at
184 | 
185 |        http://www.apache.org/licenses/LICENSE-2.0
186 | 
187 |    Unless required by applicable law or agreed to in writing, software
188 |    distributed under the License is distributed on an "AS IS" BASIS,
189 |    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
190 |    See the License for the specific language governing permissions and
191 |    limitations under the License.
192 | 


--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
 1 | # JSNES
 2 | 
 3 | A JavaScript NES emulator.
 4 | 
 5 | It's a library that works in both the browser and Node.js. The browser UI is available at [https://github.com/bfirsh/jsnes-web](https://github.com/bfirsh/jsnes-web).
 6 | 
 7 | ## Installation
 8 | 
 9 | For Node.js or Webpack:
10 | 
11 |     $ npm install jsnes
12 | 
13 | (Or `yarn add jsnes`.)
14 | 
15 | In the browser, you can use [unpkg](https://unpkg.com):
16 | 
17 | ```html
18 | <script type="text/javascript" src="https://unpkg.com/jsnes/dist/jsnes.min.js"></script>
19 | ```
20 | 
21 | ## Usage
22 | 
23 | ```javascript
24 | // Initialize and set up outputs
25 | var nes = new jsnes.NES({
26 |   onFrame: function(frameBuffer) {
27 |     // ... write frameBuffer to screen
28 |   },
29 |   onAudioSample: function(left, right) {
30 |     // ... play audio sample
31 |   }
32 | });
33 | 
34 | // Read ROM data from disk (using Node.js APIs, for the sake of this example)
35 | const fs = require('fs');
36 | var romData = fs.readFileSync('path/to/rom.nes', {encoding: 'binary'});
37 | 
38 | // Load ROM data as a string or byte array
39 | nes.loadROM(romData);
40 | 
41 | // Run frames at 60 fps, or as fast as you can.
42 | // You are responsible for reliable timing as best you can on your platform.
43 | nes.frame();
44 | nes.frame();
45 | // ...
46 | 
47 | // Hook up whatever input device you have to the controller.
48 | nes.buttonDown(1, jsnes.Controller.BUTTON_A);
49 | nes.frame();
50 | nes.buttonUp(1, jsnes.Controller.BUTTON_A);
51 | nes.frame();
52 | // ...
53 | ```
54 | 
55 | ## Build
56 | 
57 | To build a distribution:
58 | 
59 |     $ yarn run build
60 | 
61 | This will create `dist/jsnes.min.js`.
62 | 
63 | ## Running tests
64 | 
65 |     $ yarn test
66 | 
67 | ## Embedding JSNES in a web page
68 | 
69 | You can use JSNES to embed a playable version of a ROM in a web page. This is handy if you are a homebrew ROM developer and want to put a playable version of your ROM on its web page.
70 | 
71 | The best implementation is [jsnes-web](https://github.com/bfirsh/jsnes-web) but unfortunately it is not trivial to reuse the code. You'll have to copy and paste the code from that repository, the use the [`<Emulator>`](https://github.com/bfirsh/jsnes-web/blob/master/src/Emulator.js) React component. [Here is a usage example.](https://github.com/bfirsh/jsnes-web/blob/d3c35eec11986412626cbd08668dbac700e08751/src/RunPage.js#L119-L125).
72 | 
73 | A project for potential contributors (hello!): jsnes-web should be reusable and on NPM! It just needs compiling and bundling.
74 | 
75 | A more basic example is in the `example/` directory of this repository. Unfortunately this is known to be flawed, and doesn't do timing and sound as well as jsnes-web.
76 | 
77 | ## Formatting code
78 | 
79 | All code must conform to [Prettier](https://prettier.io/) formatting. The test suite won't pass unless it does.
80 | 
81 | To automatically format all your code, run:
82 | 
83 |     $ yarn run format
84 | 
85 | ## Maintainers
86 | 
87 | - [Ben Firshman](http://github.com/bfirsh)
88 | - [Ben Jones](https://github.com/BenShelton)
89 | - [Stephen Hicks](https://github.com/shicks)
90 | - [Alison Saia](https://github.com/allie)
91 | 
92 | JSNES is based on [James Sanders' vNES](https://github.com/bfirsh/vNES), and owes an awful lot to it. It also wouldn't have happened without [Matt Wescott's JSSpeccy](http://jsspeccy.zxdemo.org/), which sparked the original idea. (Ben, circa 2008: "Hmm, I wonder what else could run in a browser?!")
93 | 


--------------------------------------------------------------------------------
/example/InterglacticTransmissing.nes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bfirsh/jsnes/d8021d0336cb5c1cf924cd660ecf816bec15c11a/example/InterglacticTransmissing.nes


--------------------------------------------------------------------------------
/example/README.md:
--------------------------------------------------------------------------------
1 | An example app to demonstrate a simple way to embed JSNES.
2 | 
3 | ROM is by @slembcke: https://github.com/slembcke/InterglacticTransmissing
4 | 


--------------------------------------------------------------------------------
/example/nes-embed.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE html>
 2 | 
 3 | <html>
 4 | 	<head>
 5 | 		<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
 6 | 		<title>Embedding Example</title>
 7 | 		
 8 | 		<script type="text/javascript" src="https://unpkg.com/jsnes/dist/jsnes.min.js"></script>
 9 | 		<script type="text/javascript" src="nes-embed.js"></script>
10 | 		<script>window.onload = function(){nes_load_url("nes-canvas", "InterglacticTransmissing.nes");}</script>
11 | 	</head>
12 | 	<body>
13 | 		<div style="margin: auto; width: 75%;">
14 | 			<canvas id="nes-canvas" width="256" height="240" style="width: 100%"/>
15 | 		</div>
16 | 		<p>DPad: Arrow keys<br/>Start: Return, Select: Tab<br/>A Button: A, B Button: S</p>
17 | 	</body>
18 | </html>
19 | 


--------------------------------------------------------------------------------
/example/nes-embed.js:
--------------------------------------------------------------------------------
  1 | var SCREEN_WIDTH = 256;
  2 | var SCREEN_HEIGHT = 240;
  3 | var FRAMEBUFFER_SIZE = SCREEN_WIDTH*SCREEN_HEIGHT;
  4 | 
  5 | var canvas_ctx, image;
  6 | var framebuffer_u8, framebuffer_u32;
  7 | 
  8 | var AUDIO_BUFFERING = 512;
  9 | var SAMPLE_COUNT = 4*1024;
 10 | var SAMPLE_MASK = SAMPLE_COUNT - 1;
 11 | var audio_samples_L = new Float32Array(SAMPLE_COUNT);
 12 | var audio_samples_R = new Float32Array(SAMPLE_COUNT);
 13 | var audio_write_cursor = 0, audio_read_cursor = 0;
 14 | 
 15 | var nes = new jsnes.NES({
 16 | 	onFrame: function(framebuffer_24){
 17 | 		for(var i = 0; i < FRAMEBUFFER_SIZE; i++) framebuffer_u32[i] = 0xFF000000 | framebuffer_24[i];
 18 | 	},
 19 | 	onAudioSample: function(l, r){
 20 | 		audio_samples_L[audio_write_cursor] = l;
 21 | 		audio_samples_R[audio_write_cursor] = r;
 22 | 		audio_write_cursor = (audio_write_cursor + 1) & SAMPLE_MASK;
 23 | 	},
 24 | });
 25 | 
 26 | function onAnimationFrame(){
 27 | 	window.requestAnimationFrame(onAnimationFrame);
 28 | 	
 29 | 	image.data.set(framebuffer_u8);
 30 | 	canvas_ctx.putImageData(image, 0, 0);
 31 | }
 32 | 
 33 | function audio_remain(){
 34 | 	return (audio_write_cursor - audio_read_cursor) & SAMPLE_MASK;
 35 | }
 36 | 
 37 | function audio_callback(event){
 38 | 	var dst = event.outputBuffer;
 39 | 	var len = dst.length;
 40 | 	
 41 | 	// Attempt to avoid buffer underruns.
 42 | 	if(audio_remain() < AUDIO_BUFFERING) nes.frame();
 43 | 	
 44 | 	var dst_l = dst.getChannelData(0);
 45 | 	var dst_r = dst.getChannelData(1);
 46 | 	for(var i = 0; i < len; i++){
 47 | 		var src_idx = (audio_read_cursor + i) & SAMPLE_MASK;
 48 | 		dst_l[i] = audio_samples_L[src_idx];
 49 | 		dst_r[i] = audio_samples_R[src_idx];
 50 | 	}
 51 | 	
 52 | 	audio_read_cursor = (audio_read_cursor + len) & SAMPLE_MASK;
 53 | }
 54 | 
 55 | function keyboard(callback, event){
 56 | 	var player = 1;
 57 | 	switch(event.keyCode){
 58 | 		case 38: // UP
 59 | 			callback(player, jsnes.Controller.BUTTON_UP); break;
 60 | 		case 40: // Down
 61 | 			callback(player, jsnes.Controller.BUTTON_DOWN); break;
 62 | 		case 37: // Left
 63 | 			callback(player, jsnes.Controller.BUTTON_LEFT); break;
 64 | 		case 39: // Right
 65 | 			callback(player, jsnes.Controller.BUTTON_RIGHT); break;
 66 | 		case 65: // 'a' - qwerty, dvorak
 67 | 		case 81: // 'q' - azerty
 68 | 			callback(player, jsnes.Controller.BUTTON_A); break;
 69 | 		case 83: // 's' - qwerty, azerty
 70 | 		case 79: // 'o' - dvorak
 71 | 			callback(player, jsnes.Controller.BUTTON_B); break;
 72 | 		case 9: // Tab
 73 | 			callback(player, jsnes.Controller.BUTTON_SELECT); break;
 74 | 		case 13: // Return
 75 | 			callback(player, jsnes.Controller.BUTTON_START); break;
 76 | 		default: break;
 77 | 	}
 78 | }
 79 | 
 80 | function nes_init(canvas_id){
 81 | 	var canvas = document.getElementById(canvas_id);
 82 | 	canvas_ctx = canvas.getContext("2d");
 83 | 	image = canvas_ctx.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
 84 | 	
 85 | 	canvas_ctx.fillStyle = "black";
 86 | 	canvas_ctx.fillRect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT);
 87 | 	
 88 | 	// Allocate framebuffer array.
 89 | 	var buffer = new ArrayBuffer(image.data.length);
 90 | 	framebuffer_u8 = new Uint8ClampedArray(buffer);
 91 | 	framebuffer_u32 = new Uint32Array(buffer);
 92 | 	
 93 | 	// Setup audio.
 94 | 	var audio_ctx = new window.AudioContext();
 95 | 	var script_processor = audio_ctx.createScriptProcessor(AUDIO_BUFFERING, 0, 2);
 96 | 	script_processor.onaudioprocess = audio_callback;
 97 | 	script_processor.connect(audio_ctx.destination);
 98 | }
 99 | 
100 | function nes_boot(rom_data){
101 | 	nes.loadROM(rom_data);
102 | 	window.requestAnimationFrame(onAnimationFrame);
103 | }
104 | 
105 | function nes_load_data(canvas_id, rom_data){
106 | 	nes_init(canvas_id);
107 | 	nes_boot(rom_data);
108 | }
109 | 
110 | function nes_load_url(canvas_id, path){
111 | 	nes_init(canvas_id);
112 | 	
113 | 	var req = new XMLHttpRequest();
114 | 	req.open("GET", path);
115 | 	req.overrideMimeType("text/plain; charset=x-user-defined");
116 | 	req.onerror = () => console.log(`Error loading ${path}: ${req.statusText}`);
117 | 	
118 | 	req.onload = function() {
119 | 		if (this.status === 200) {
120 | 		nes_boot(this.responseText);
121 | 		} else if (this.status === 0) {
122 | 			// Aborted, so ignore error
123 | 		} else {
124 | 			req.onerror();
125 | 		}
126 | 	};
127 | 	
128 | 	req.send();
129 | }
130 | 
131 | document.addEventListener('keydown', (event) => {keyboard(nes.buttonDown, event)});
132 | document.addEventListener('keyup', (event) => {keyboard(nes.buttonUp, event)});
133 | 


--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
 1 | {
 2 |   "name": "jsnes",
 3 |   "version": "1.2.1",
 4 |   "description": "A JavaScript NES emulator",
 5 |   "homepage": "https://github.com/bfirsh/jsnes",
 6 |   "author": "Ben Firshman <ben@firshman.co.uk> (https://fir.sh)",
 7 |   "main": "src/index.js",
 8 |   "repository": {
 9 |     "type": "git",
10 |     "url": "git://github.com/bfirsh/jsnes.git"
11 |   },
12 |   "license": "Apache-2.0",
13 |   "scripts": {
14 |     "build": "webpack",
15 |     "test": "prettier-check src/**/*.js && mocha ./test/*.spec.js",
16 |     "test:watch": "mocha -w ./test/*.spec.js",
17 |     "prepublish": "npm run build",
18 |     "format": "prettier --write src/**/*.js"
19 |   },
20 |   "devDependencies": {
21 |     "chai": "^4.1.2",
22 |     "eslint": "^6.8.0",
23 |     "eslint-config-prettier": "^6.10.1",
24 |     "eslint-loader": "^2.0.0",
25 |     "mocha": "^9.1.1",
26 |     "prettier": "^2.0.5",
27 |     "prettier-check": "^2.0.0",
28 |     "sinon": "^9.0.1",
29 |     "uglifyjs-webpack-plugin": "^1.3.0",
30 |     "webpack": "^3.9.1"
31 |   }
32 | }
33 | 


--------------------------------------------------------------------------------
/roms/croom/README.html:
--------------------------------------------------------------------------------
 1 | <!DOCTYPE HTML><html><head>
 2 | <title>Concentration Room</title>
 3 | <link rel="stylesheet" type="text/css" href="docs/croom.css">
 4 | </head><body>
 5 | <div id="pgheader"><div class="thereisnosuchthingaspaddingauto">
 6 | <h1><img alt="Concentration Room" width="320" height="128" src="docs/croomlogo320.png"></h1>
 7 | <ul id="headerlist">
 8 | <li><strong>About</strong>
 9 | <ul>
10 | <li><a href="#overview">Overview</a>
11 | <li><a href="#requirements">Requirements</a>
12 | <li><a href="#modes">Modes</a>
13 | <li><a href="#faq">FAQ</a>
14 | </ul>
15 | <li><a href="http://pineight.com/croom/dl">Download</a>
16 | </ul>
17 | 
18 | </div></div><div id="pgbody"><div class="thereisnosuchthingaspaddingauto">
19 | 
20 | <h2><a name="overview">Overview</a></h2>
21 | <img src="docs/croom_screenshot01.png" style="float:right; margin: 0 0 1em 1em">
22 | <p>
23 | An accident at the biochemical lab has released a neurotoxin,
24 | and you've been quarantined after exposure.  Maintain your
25 | sanity by playing a card-matching game.
26 | </p><p>
27 | The table is littered with 10, 20, 36, 52, or 72 face-down cards.
28 | Flip two cards, and if they show the same emblem, you keep them.
29 | If they don't, flip them back.
30 | </p>
31 | 
32 | <h2><a name="requirements">System Requirements</a></h2>
33 | <p>
34 | Concentration Room is designed for your Nintendo Entertainment System. This version is an NROM-128 (16 KiB PRG, 8 KiB CHR), and it has been tested on a <a title="CompactFlash to NES adapter" href="http://www.retrousb.com/index.php?cPath=24">PowerPak</a>. It also works in PC-based emulators such as <a href="http://nestopia.sourceforge.net/">Nestopia</a> and <a href="http://fceux.com/web/home.html">FCE Ultra</a>.
35 | </p>
36 | 
37 | <h2><a name="modes">Modes</a></h2>
38 | <dl>
39 | <dt>1 Player Story<dd>
40 | Play solitaire to start to work the toxin out of your system.  Then defeat other contaminated technicians and children one on one.
41 | <dt>1 Player Solitaire<dd>
42 | Select a difficulty level, then try to clear the table without having to turn back more than 99 non-matching pairs.
43 | <dt>2 Players<dd>
44 | Two players take turns turning over cards.  They can pass one controller back and forth or use one controller each.  If a pair doesn't match, the other player presses the A and B Buttons and takes a turn. The first player to take half the pairs wins.
45 | <dt>Vs. CPU<dd>
46 | Like 2 Players, except the second player is controlled by the NES.
47 | </dl>
48 | 
49 | <h2><a name="faq">FAQ (Fully Anticipated Questions)</a></h2>
50 | <dl>
51 | <dt>How long have you been working on this?<dd>
52 | This is actually my third try. The logo and the earliest background sketch date back to 2000. It got held up because I lacked artistic skill on the 16x16 pixel canvas. The second try in 2007 finalized the appearance of the game, and I did some work on the "emblem designer" that will show up in a future release. In late November 2009, I discovered <a title="Review of Dian Shi Mali on waluigious.com" href="http://www.waluigious.com/2008/09/in-which-dian-shi-ma-li.html"><i>Dian Shi Mali</i></a>, a <a title="Dian Shi Mali article on Wikipedia" href="http://en.wikipedia.org/wiki/Dian_Shi_Mali">gambling simulator</a> for the Famicom (Asian version of the NES) that also uses 16x16 pixel emblems. After a few hours of <a title="Video of Dian Shi Mali play" href="http://www.youtube.com/watch?v=4s1mAPISOzw">pushing Start to rich</a>, I was inspired to create a set of 36 emblems. By then, I was ready to code most of the game in spare time during December 2009.
53 | <dt>Why are you still making games that don't scroll? You're better than that, as I saw in the <a title="Video of a homebrew sidescroller engine" href="http://www.youtube.com/watch?v=GY693NxC9xU">President video</a>.<dd>
54 | I saw it as something simple that I could finish fairly quickly in order to push falling block games off <a href="http://www.pineight.com/">the front page of my web site</a>.
55 | <dt>GameTek already made two other Concentration games on the NES. Why did you make this one?<dd>
56 | The controls in <i>I Can Remember</i> nor <i>Classic Concentration</i> are clunky. Neither of them features a full 72-card deck. And of course, they're not <a title="Free Software Definition (free speech, not free beer)" href="http://www.gnu.org/philosophy/free-sw.html">free software</a>.
57 | <dt>In vs. modes, why end the game at half the cards matched instead of one more than half?<dd>
58 | Pairs early in a game require more skill to clear, and the last pair requires absolutely no skill.  For example, a 20-card game tied at 4-4 will always end up 6-4.  And at 5-3, the player in the lead likely got more early matches.  So if we award no points for the last pair, the first player to reach half always wins.
59 | <dt>What's that font?<dd>
60 | The font in the game's logo is called <a href="http://www.windowfonts.com/fonts/wasted-collection.html">Wasted Collection</a>. The font in <a title="Launcher for small programs for Game Boy Advance" href="http://www.pineight.com/gba/#mbmenu">Multiboot Menu</a> was based on it. The monospace font for menu text originally appeared in the "Who's Cuter" demo and is based on <a href="http://en.wikipedia.org/wiki/Chicago_%28typeface%29">Apple Chicago by Susan Kare</a>. (Another fun font is on <a href="http://www.angelfire.com/stars5/tkcpics2/wildworld/#downloads">this page</a>.)
61 | <dt>Are you a Nazi?<dd>
62 | No, and that's why this game is called Concentration <em>Room,</em> not <a title="National Lampoon video" href="http://www.youtube.com/watch?v=cXeHn9k27Iw">Concentration Camp</a>.
63 | </dl>
64 | <h2>Legal</h2>
65 | <p>
66 | Copyright &copy; 2010 Damian Yerrick &lt;croom&#64;pineight.com&gt;
67 | </p><p>
68 | Copying and distribution of this file, with or without modification, are permitted in any medium without royalty provided the copyright notice and this notice are preserved.  This file is offered as-is, without any warranty.
69 | </p><p>
70 | The accompanying program is free software: you can redistribute it and/or modify it under the terms of the <a href="http://www.gnu.org/copyleft/gpl.html">GNU General Public License</a>, version 3 or later. As a special exception, you may copy and distribute exact copies of the program, as published by Damian Yerrick, in iNES or UNIF executable form without source code.
71 | </p><p>
72 | This product is not sponsored or endorsed by Nintendo, Ravensburger, Hasbro, Mattel, Quaker Oats, NBC Universal, GameTek, or Apple.
73 | </p>
74 | </div></div>
75 | </body></html>
76 | 


--------------------------------------------------------------------------------
/roms/croom/croom.nes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bfirsh/jsnes/d8021d0336cb5c1cf924cd660ecf816bec15c11a/roms/croom/croom.nes


--------------------------------------------------------------------------------
/roms/lj65/README.txt:
--------------------------------------------------------------------------------
  1 |  _     _     __     ___
  2 | | |   (_)   / /    / __|
  3 | | |    _   / /_   | /__
  4 | | |   | | |  _ \  |___ \
  5 | | |_  | | | (_) | .___) |
  6 |  \__|_| |  \___/   \___/
  7 |     |__/
  8 | 
  9 | LJ65
 10 | an NES game
 11 | by Damian Yerrick
 12 | 
 13 | See the legal section below.
 14 | 
 15 | _____________________________________________________________________
 16 | Introduction
 17 | 
 18 | LJ65 is an action puzzle game for NES comparable to the popular
 19 | game Tetris(R), except distributed as free software and with more
 20 | responsive movement controls.
 21 | 
 22 | _____________________________________________________________________
 23 | Installing
 24 | 
 25 | LJ65 is designed to run on Nintendo Entertainment System (called
 26 | Family Computer in Japan) and accurate NES emulators.  It is
 27 | distributed as source code and an iNES format binary, using mapper
 28 | 0 (NROM).  Separate binaries for NTSC and PAL systems are provided.
 29 | 
 30 | This program has been tested on NES using a PowerPak.  It also works
 31 | on the current versions of Nintendulator, Nestopia, and FCE Ultra.
 32 | (Do not use the outdated Nesticle emulator anymore.)
 33 | 
 34 | To run LJ65 on an NES without buying a PowerPak, you'll need to
 35 | solder together an NES cartridge with at least 16 KB of PRG space
 36 | and 4 KB of CHR space.  A modded NROM-128 or CNROM board should be
 37 | fine.  Chris Covell has put together instructions on how to replace
 38 | NES Game Paks' mask ROM chips with writable EEPROMs.
 39 | http://www.zyx.com/chrisc/solarwarscart.html
 40 | 
 41 | To build LJ65 from source code, you will need
 42 |   * CC65 (from http://www.cc65.org/ but you don't need the
 43 |     non-free C compiler)
 44 |   * GNU Make and Coreutils (included with most Linux distributions;
 45 |     Windows users can use MSYS from http://www.devkitpro.org/)
 46 | 
 47 | Modify the makefile to point to where you have CC65 installed.
 48 | Then run make.  (Windows users can run mk.bat instead, which runs
 49 | make in the correct folder.)  On a desktop PC from late 2000 with
 50 | a Pentium III 866 MHz, recompiling the whole thing takes about one
 51 | second.  To build some data conversion tools, you'll need a GNU C
 52 | compiler such as MinGW; I have included Windows binaries of the
 53 | conversion tools for those who want to quickly get into hacking
 54 | on LJ65.
 55 | 
 56 | _____________________________________________________________________
 57 | Game controls
 58 | 
 59 | Title screen:
 60 |   Start: Show playfields.
 61 | Game over:
 62 |   A+B: Join game.
 63 | Menu:
 64 |   Control Pad up, down: Move cursor.
 65 |   Control Pad left, right: Change option at cursor.
 66 |   A: Start game.
 67 | Game:
 68 |   Control Pad left, right, down: Move piece.
 69 |   Control Pad up: Move piece to floor.
 70 |   Control Pad up, down once landed: Lock piece into place.
 71 |   A: Rotate piece clockwise.
 72 |   B: Rotate piece anticlockwise.
 73 |   Start: Pause game.
 74 | 
 75 | _____________________________________________________________________
 76 | Play
 77 | 
 78 | At first, press Start to skip past each of the informational screens.
 79 | Then press Start at the title screen to display the playfields.
 80 | At this point, either player can press the A and B buttons at the
 81 | same time to begin playing.
 82 | 
 83 | The pieces in LJ65 are called tetrominoes.  (The word comes from
 84 | tetra-, a Greek prefix meaning four, and -omino, as in domino or
 85 | pentomino.)  Each of the seven tetrominoes is made of four square
 86 | blocks and named after a letter of the Latin alphabet that it
 87 | resembles:
 88 |            _           _   ___     ___     _     ___
 89 |  _______  | |___   ___| | |   |  _|  _|  _| |_  |_  |_
 90 | |_______| |_____| |_____| |___| |___|   |_____|   |___|
 91 |     I        J       L      O      S       T       Z
 92 | 
 93 | When you start the game, a tetromino will begin to fall slowly into
 94 | the bin.  You can move it with the Control Pad and rotate it with
 95 | the A or B button.
 96 | 
 97 | The goal of LJ65 is to make complete horizontal lines by
 98 | packing the pieces into the bin with no holes.  If you complete
 99 | a line, everything above it will move down a row.  If you complete
100 | more than one line with a piece, you get more points.
101 | 
102 | As you play, the pieces will gradually fall faster, making the game
103 | more difficult.  At some point, the pieces will fall so fast that
104 | they appear immediately at the bottom row of the playfield.  If you
105 | fill the bin to the top, to the point where more pieces cannot enter,
106 | you "top out" and the game ends.
107 | 
108 | If you have an overhang in the blocks, you can slide another
109 | piece under it by holding Left or Right as the new piece passes
110 | by the overhang:
111 |        _
112 |       | |
113 |      _| |
114 |     |___|
115 |    _            _   _        _ _
116 |  _| |     =>  _| | | | =>  _| | |
117 | |  _|        |  _|_| |    |  _| |
118 | |_|          |_| |___|    |_|___|
119 | 
120 | Or in some cases, you can rotate pieces into very tight spaces:
121 |      _
122 |    _| |
123 |   |_  |
124 |     |_|
125 |  _     ___      _   _ ___      _     ___
126 | | |   |_  | => | |_| |_  | => | |___|_  |
127 | | |_   _| |    | |_  |_| |    | |_   _| |
128 | |___| |___|    |___|_|___|    |___|_|___|
129 | 
130 | _____________________________________________________________________
131 | Rotation systems
132 | 
133 | LJ65 supports two rotation systems, which it calls "Center" and
134 | "Bottom".  Center implements rules more familiar to Western players,
135 | while Bottom pleases fans of the Japanese arcade tradition.
136 | 
137 | In Center, pieces start out with their flat side down, and they
138 | rotate around the center of an imaginary 3x3 or 4x4 cell bounding
139 | box.  If this is blocked, try one square to the right, one square to
140 | the left, and finally one square up.
141 | Up locks a piece into place immediately, and down waits for another
142 | press of up or down before locking the piece.
143 | After a piece locks, the next one comes out immediately, but after
144 | the pieces have sped up enough, the next piece waits a bit.
145 | Colors match the so-called Guideline: I is turquoise.
146 | 
147 | . [].   . [].   . . .   . [].       . [][]  . [].   . . .   []. .
148 | [][][]  . [][]  [][][]  [][].       [][].   . [][]  . [][]  [][].
149 | . . .   . [].   . [].   . [].       . . .   . . []  [][].   . [].
150 | Figure: T and S rotation in Center
151 | 
152 | In Bottom, the J, L, S, T, and Z pieces start out with their flat
153 | side up, and they rotate to stay in contact with the bottom of an
154 | imaginary 3x3 cell box.  S and Z pieces also keep a block in the
155 | bottom center of this box.  If this is blocked by a wall or a block
156 | outside the piece's central column, then try one square to the right,
157 | one square to the left, and finally (in the case of T) one square up.
158 | Down locks on contact, and up waits for another press of up or down
159 | to lock.  After a piece locks, the next one waits a bit to come out.
160 | Colors match those from a game with a monkey: I is red.
161 | 
162 | . . .   . [].   . . .   . [].       . . .   []. .   . . .   []. .
163 | [][][]  [][].   . [].   . [][]      . [][]  [][].   . [][]  [][].
164 | . [].   . [].   [][][]  . [].       [][].   . [].   [][].   . [].
165 | Figure: T and S rotation in Bottom
166 | 
167 | _____________________________________________________________________
168 | Scoring
169 | 
170 | Use up or down on the Control Pad to drop pieces, and you'll get
171 | one point per row that the piece moves down.
172 | 
173 | You also get points for clearing lines.  Clearing more lines
174 | with a single piece is worth more points:
175 | 
176 | SINGLE   (1 line with any piece)       1 * 1 * 100 =  100 points
177 | DOUBLE   (2 lines with any piece)      2 * 2 * 100 =  400 points
178 | TRIPLE   (3 lines with I, J, or L)     3 * 3 * 100 =  900 points
179 | HOME RUN (4 lines with I only)         4 * 4 * 100 = 1600 points
180 | 
181 | Making lines with consecutive pieces is called a combo and is
182 | worth even more points.  In general, the score for a line clear
183 | is the number of lines cleared with this piece, times the number
184 | of lines cleared so far in this combo, times 100.  For example,
185 | a double-triple-single combo is worth a total of 2300 points:
186 | 
187 | 2 lines      2 * 2 * 100 =  200 points
188 | 3 lines      3 * 5 * 100 = 1500 points
189 | 1 line       1 * 6 * 100 =  600 points
190 | 
191 | When you start clearing lines, the game shows how many lines you
192 | made in this combo.  If you leave a 2-block-wide hole at the side
193 | of the bin, you might manage to make a combo of 12 lines or more.
194 | But then you have to weigh this against keeping your stack low
195 | and earning more drop bonus.
196 | 
197 | There are some grandmasters who can get millions of points in
198 | some puzzle games.  There exists a known corner case in this
199 | game's score computation, and scoring is expected to fail beyond
200 | 6,553,000 points.
201 | 
202 | If two players are playing, and you have GARBAGE turned on in the
203 | menu, and you complete more than one line with a piece, the other
204 | player's field rises by one or more rows:
205 | 
206 | DOUBLE:   1 line
207 | TRIPLE:   2 lines
208 | HOME RUN: 4 lines
209 | 
210 | This is not affected by combos.
211 | 
212 | _____________________________________________________________________
213 | Keypress codes
214 | 
215 | Some of the lesser-used features of the game are hidden so that
216 | players interested in the most common features don't become confused.
217 | 
218 | At title screen:
219 |   * B + Left hides the ghost piece.
220 | 
221 | _____________________________________________________________________
222 | Questions
223 | 
224 | Q: Isn't this a copy of Tetris?
225 | 
226 | Yes, in part, but we don't believe it infringes Tetris Holding's
227 | copyright.  It was developed by people who had not read the source
228 | code of Tetris.  We disagree with Tetris Holding's claim of broad
229 | patent-like rights over the game.  Any similarity between LJ65 and
230 | Tetris is a consequence of common methods of operation, which are
231 | excluded from U.S. copyright (17 USC 102(b)).
232 | 
233 | Q: Where's (feature that has appeared in another game)?
234 | 
235 | If it's mentioned in the "future" list at the bottom of CHANGES.txt,
236 | I know about it, and you may see some of those issues resolved in
237 | the next version.  Otherwise, I'd be glad to take suggestions,
238 | provided that they aren't "network play with no lag" or "make the
239 | game just like that Japanese game I saw on YouTube".
240 | 
241 | Q: Why aren't the blocks square on my TV?
242 | 
243 | In NTSC, a square pixel is 7/24 of a color subcarrier period wide
244 | in 480i mode or 7/12 of a period in the so-called "240p" mode.
245 | But like the video chipsets in most 8-bit and 16-bit computing
246 | platforms, the NES PPU generates pixels that are not square:
247 | 8/12 of a period instead of 7/12.  Games for PC, Apple II, or any
248 | other platform with frame buffer video could correct for this by
249 | drawing differently sized tiles, but games for NES are limited to
250 | an 8x8 pixel tile grid.  PAL video and widescreen televisions make
251 | the problem even more pronounced.
252 | 
253 | Q: Why do some pieces change color subtly when they land?
254 | 
255 | The NES's tile size is 8x8 pixels, but the "attribute table"
256 | assigns palettes to 16x16 pixel areas, or clusters of 2x2 tiles.
257 | Only three colors plus the backdrop color can appear in each
258 | color area.  So the game approximates the color of each piece as a
259 | combination of blue, orange, and green throughout the screen.
260 | 
261 | The MMC5 mapper has ExGrafix, which allows 8x8 pixel color areas.
262 | But the only source of MMC5 hardware is used copies of Castlevania
263 | III: Dracula's Curse and Koei's war sims, unlike the discrete mapper
264 | boards that retrousb.com sells.
265 | 
266 | Q: Who is the fellow on How to Play, and where are his legs?
267 | 
268 | Who are you, and where is your tail? ;-)
269 | 
270 | _____________________________________________________________________
271 | Credits
272 | 
273 | Program and graphics by Damian Yerrick
274 | Original game design by Alexey Pajitnov
275 | NES assembler toolchain by Ullrich von Bassewitz
276 | NES emulators by Xodnizel, Martin Freij, and Quietust
277 | NES documentation by contributors to http://nesdevwiki.org/
278 | 
279 | Music:
280 |   TEMP is "Tetris New Melody (OCRemoved)" by Am.Fm.GM
281 |   K.231 is "Leck mich im Arsch" by Wolfgang A. Mozart
282 | 
283 | _____________________________________________________________________
284 | Legal
285 | 
286 | Copyright (c) 2009 Damian Yerrick
287 | 
288 | This manual is under the following license:
289 | 
290 |   This work is provided 'as-is', without any express or implied
291 |   warranty. In no event will the authors be held liable for any
292 |   damages arising from the use of this work.
293 | 
294 |   Permission is granted to anyone to use this work for any
295 |   purpose, including commercial applications, and to alter it and
296 |   redistribute it freely, subject to the following restrictions:
297 | 
298 |    1. The origin of this work must not be misrepresented; you
299 |       must not claim that you wrote the original work. If you use
300 |       this work in a product, an acknowledgment in the product
301 |       documentation would be appreciated but is not required.
302 |    2. Altered source versions must be plainly marked as such,
303 |       and must not be misrepresented as being the original work.
304 |    3. This notice may not be removed or altered from any
305 |       source distribution.
306 | 
307 |   The term "source" refers to the preferred form of a work for making
308 |   changes to it. 
309 | 
310 | The LJ65 software described by this manual is distributed under
311 | the GNU General Public License, version 2 or later, with ABSOLUTELY
312 | NO WARRANTY.  See GPL.txt for details.
313 | 
314 | LJ65 is not a Tetris product and is not endorsed by Tetris Holding.
315 | 


--------------------------------------------------------------------------------
/roms/lj65/lj65.nes:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/bfirsh/jsnes/d8021d0336cb5c1cf924cd660ecf816bec15c11a/roms/lj65/lj65.nes


--------------------------------------------------------------------------------
/src/controller.js:
--------------------------------------------------------------------------------
 1 | var Controller = function () {
 2 |   this.state = new Array(8);
 3 |   for (var i = 0; i < this.state.length; i++) {
 4 |     this.state[i] = 0x40;
 5 |   }
 6 | };
 7 | 
 8 | Controller.BUTTON_A = 0;
 9 | Controller.BUTTON_B = 1;
10 | Controller.BUTTON_SELECT = 2;
11 | Controller.BUTTON_START = 3;
12 | Controller.BUTTON_UP = 4;
13 | Controller.BUTTON_DOWN = 5;
14 | Controller.BUTTON_LEFT = 6;
15 | Controller.BUTTON_RIGHT = 7;
16 | 
17 | Controller.prototype = {
18 |   buttonDown: function (key) {
19 |     this.state[key] = 0x41;
20 |   },
21 | 
22 |   buttonUp: function (key) {
23 |     this.state[key] = 0x40;
24 |   },
25 | };
26 | 
27 | module.exports = Controller;
28 | 


--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 |   Controller: require("./controller"),
3 |   NES: require("./nes"),
4 | };
5 | 


--------------------------------------------------------------------------------
/src/mappers.js:
--------------------------------------------------------------------------------
   1 | var utils = require("./utils");
   2 | 
   3 | var Mappers = {};
   4 | 
   5 | Mappers[0] = function (nes) {
   6 |   this.nes = nes;
   7 | };
   8 | 
   9 | Mappers[0].prototype = {
  10 |   reset: function () {
  11 |     this.joy1StrobeState = 0;
  12 |     this.joy2StrobeState = 0;
  13 |     this.joypadLastWrite = 0;
  14 | 
  15 |     this.zapperFired = false;
  16 |     this.zapperX = null;
  17 |     this.zapperY = null;
  18 |   },
  19 | 
  20 |   write: function (address, value) {
  21 |     if (address < 0x2000) {
  22 |       // Mirroring of RAM:
  23 |       this.nes.cpu.mem[address & 0x7ff] = value;
  24 |     } else if (address > 0x4017) {
  25 |       this.nes.cpu.mem[address] = value;
  26 |       if (address >= 0x6000 && address < 0x8000) {
  27 |         // Write to persistent RAM
  28 |         this.nes.opts.onBatteryRamWrite(address, value);
  29 |       }
  30 |     } else if (address > 0x2007 && address < 0x4000) {
  31 |       this.regWrite(0x2000 + (address & 0x7), value);
  32 |     } else {
  33 |       this.regWrite(address, value);
  34 |     }
  35 |   },
  36 | 
  37 |   writelow: function (address, value) {
  38 |     if (address < 0x2000) {
  39 |       // Mirroring of RAM:
  40 |       this.nes.cpu.mem[address & 0x7ff] = value;
  41 |     } else if (address > 0x4017) {
  42 |       this.nes.cpu.mem[address] = value;
  43 |     } else if (address > 0x2007 && address < 0x4000) {
  44 |       this.regWrite(0x2000 + (address & 0x7), value);
  45 |     } else {
  46 |       this.regWrite(address, value);
  47 |     }
  48 |   },
  49 | 
  50 |   load: function (address) {
  51 |     // Wrap around:
  52 |     address &= 0xffff;
  53 | 
  54 |     // Check address range:
  55 |     if (address > 0x4017) {
  56 |       // ROM:
  57 |       return this.nes.cpu.mem[address];
  58 |     } else if (address >= 0x2000) {
  59 |       // I/O Ports.
  60 |       return this.regLoad(address);
  61 |     } else {
  62 |       // RAM (mirrored)
  63 |       return this.nes.cpu.mem[address & 0x7ff];
  64 |     }
  65 |   },
  66 | 
  67 |   regLoad: function (address) {
  68 |     switch (
  69 |       address >> 12 // use fourth nibble (0xF000)
  70 |     ) {
  71 |       case 0:
  72 |         break;
  73 | 
  74 |       case 1:
  75 |         break;
  76 | 
  77 |       case 2:
  78 |       // Fall through to case 3
  79 |       case 3:
  80 |         // PPU Registers
  81 |         switch (address & 0x7) {
  82 |           case 0x0:
  83 |             // 0x2000:
  84 |             // PPU Control Register 1.
  85 |             // (the value is stored both
  86 |             // in main memory and in the
  87 |             // PPU as flags):
  88 |             // (not in the real NES)
  89 |             return this.nes.cpu.mem[0x2000];
  90 | 
  91 |           case 0x1:
  92 |             // 0x2001:
  93 |             // PPU Control Register 2.
  94 |             // (the value is stored both
  95 |             // in main memory and in the
  96 |             // PPU as flags):
  97 |             // (not in the real NES)
  98 |             return this.nes.cpu.mem[0x2001];
  99 | 
 100 |           case 0x2:
 101 |             // 0x2002:
 102 |             // PPU Status Register.
 103 |             // The value is stored in
 104 |             // main memory in addition
 105 |             // to as flags in the PPU.
 106 |             // (not in the real NES)
 107 |             return this.nes.ppu.readStatusRegister();
 108 | 
 109 |           case 0x3:
 110 |             return 0;
 111 | 
 112 |           case 0x4:
 113 |             // 0x2004:
 114 |             // Sprite Memory read.
 115 |             return this.nes.ppu.sramLoad();
 116 |           case 0x5:
 117 |             return 0;
 118 | 
 119 |           case 0x6:
 120 |             return 0;
 121 | 
 122 |           case 0x7:
 123 |             // 0x2007:
 124 |             // VRAM read:
 125 |             return this.nes.ppu.vramLoad();
 126 |         }
 127 |         break;
 128 |       case 4:
 129 |         // Sound+Joypad registers
 130 |         switch (address - 0x4015) {
 131 |           case 0:
 132 |             // 0x4015:
 133 |             // Sound channel enable, DMC Status
 134 |             return this.nes.papu.readReg(address);
 135 | 
 136 |           case 1:
 137 |             // 0x4016:
 138 |             // Joystick 1 + Strobe
 139 |             return this.joy1Read();
 140 | 
 141 |           case 2:
 142 |             // 0x4017:
 143 |             // Joystick 2 + Strobe
 144 |             // https://wiki.nesdev.com/w/index.php/Zapper
 145 |             var w;
 146 | 
 147 |             if (
 148 |               this.zapperX !== null &&
 149 |               this.zapperY !== null &&
 150 |               this.nes.ppu.isPixelWhite(this.zapperX, this.zapperY)
 151 |             ) {
 152 |               w = 0;
 153 |             } else {
 154 |               w = 0x1 << 3;
 155 |             }
 156 | 
 157 |             if (this.zapperFired) {
 158 |               w |= 0x1 << 4;
 159 |             }
 160 |             return (this.joy2Read() | w) & 0xffff;
 161 |         }
 162 |         break;
 163 |     }
 164 |     return 0;
 165 |   },
 166 | 
 167 |   regWrite: function (address, value) {
 168 |     switch (address) {
 169 |       case 0x2000:
 170 |         // PPU Control register 1
 171 |         this.nes.cpu.mem[address] = value;
 172 |         this.nes.ppu.updateControlReg1(value);
 173 |         break;
 174 | 
 175 |       case 0x2001:
 176 |         // PPU Control register 2
 177 |         this.nes.cpu.mem[address] = value;
 178 |         this.nes.ppu.updateControlReg2(value);
 179 |         break;
 180 | 
 181 |       case 0x2003:
 182 |         // Set Sprite RAM address:
 183 |         this.nes.ppu.writeSRAMAddress(value);
 184 |         break;
 185 | 
 186 |       case 0x2004:
 187 |         // Write to Sprite RAM:
 188 |         this.nes.ppu.sramWrite(value);
 189 |         break;
 190 | 
 191 |       case 0x2005:
 192 |         // Screen Scroll offsets:
 193 |         this.nes.ppu.scrollWrite(value);
 194 |         break;
 195 | 
 196 |       case 0x2006:
 197 |         // Set VRAM address:
 198 |         this.nes.ppu.writeVRAMAddress(value);
 199 |         break;
 200 | 
 201 |       case 0x2007:
 202 |         // Write to VRAM:
 203 |         this.nes.ppu.vramWrite(value);
 204 |         break;
 205 | 
 206 |       case 0x4014:
 207 |         // Sprite Memory DMA Access
 208 |         this.nes.ppu.sramDMA(value);
 209 |         break;
 210 | 
 211 |       case 0x4015:
 212 |         // Sound Channel Switch, DMC Status
 213 |         this.nes.papu.writeReg(address, value);
 214 |         break;
 215 | 
 216 |       case 0x4016:
 217 |         // Joystick 1 + Strobe
 218 |         if ((value & 1) === 0 && (this.joypadLastWrite & 1) === 1) {
 219 |           this.joy1StrobeState = 0;
 220 |           this.joy2StrobeState = 0;
 221 |         }
 222 |         this.joypadLastWrite = value;
 223 |         break;
 224 | 
 225 |       case 0x4017:
 226 |         // Sound channel frame sequencer:
 227 |         this.nes.papu.writeReg(address, value);
 228 |         break;
 229 | 
 230 |       default:
 231 |         // Sound registers
 232 |         // console.log("write to sound reg");
 233 |         if (address >= 0x4000 && address <= 0x4017) {
 234 |           this.nes.papu.writeReg(address, value);
 235 |         }
 236 |     }
 237 |   },
 238 | 
 239 |   joy1Read: function () {
 240 |     var ret;
 241 | 
 242 |     switch (this.joy1StrobeState) {
 243 |       case 0:
 244 |       case 1:
 245 |       case 2:
 246 |       case 3:
 247 |       case 4:
 248 |       case 5:
 249 |       case 6:
 250 |       case 7:
 251 |         ret = this.nes.controllers[1].state[this.joy1StrobeState];
 252 |         break;
 253 |       case 8:
 254 |       case 9:
 255 |       case 10:
 256 |       case 11:
 257 |       case 12:
 258 |       case 13:
 259 |       case 14:
 260 |       case 15:
 261 |       case 16:
 262 |       case 17:
 263 |       case 18:
 264 |         ret = 0;
 265 |         break;
 266 |       case 19:
 267 |         ret = 1;
 268 |         break;
 269 |       default:
 270 |         ret = 0;
 271 |     }
 272 | 
 273 |     this.joy1StrobeState++;
 274 |     if (this.joy1StrobeState === 24) {
 275 |       this.joy1StrobeState = 0;
 276 |     }
 277 | 
 278 |     return ret;
 279 |   },
 280 | 
 281 |   joy2Read: function () {
 282 |     var ret;
 283 | 
 284 |     switch (this.joy2StrobeState) {
 285 |       case 0:
 286 |       case 1:
 287 |       case 2:
 288 |       case 3:
 289 |       case 4:
 290 |       case 5:
 291 |       case 6:
 292 |       case 7:
 293 |         ret = this.nes.controllers[2].state[this.joy2StrobeState];
 294 |         break;
 295 |       case 8:
 296 |       case 9:
 297 |       case 10:
 298 |       case 11:
 299 |       case 12:
 300 |       case 13:
 301 |       case 14:
 302 |       case 15:
 303 |       case 16:
 304 |       case 17:
 305 |       case 18:
 306 |         ret = 0;
 307 |         break;
 308 |       case 19:
 309 |         ret = 1;
 310 |         break;
 311 |       default:
 312 |         ret = 0;
 313 |     }
 314 | 
 315 |     this.joy2StrobeState++;
 316 |     if (this.joy2StrobeState === 24) {
 317 |       this.joy2StrobeState = 0;
 318 |     }
 319 | 
 320 |     return ret;
 321 |   },
 322 | 
 323 |   loadROM: function () {
 324 |     if (!this.nes.rom.valid || this.nes.rom.romCount < 1) {
 325 |       throw new Error("NoMapper: Invalid ROM! Unable to load.");
 326 |     }
 327 | 
 328 |     // Load ROM into memory:
 329 |     this.loadPRGROM();
 330 | 
 331 |     // Load CHR-ROM:
 332 |     this.loadCHRROM();
 333 | 
 334 |     // Load Battery RAM (if present):
 335 |     this.loadBatteryRam();
 336 | 
 337 |     // Reset IRQ:
 338 |     //nes.getCpu().doResetInterrupt();
 339 |     this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
 340 |   },
 341 | 
 342 |   loadPRGROM: function () {
 343 |     if (this.nes.rom.romCount > 1) {
 344 |       // Load the two first banks into memory.
 345 |       this.loadRomBank(0, 0x8000);
 346 |       this.loadRomBank(1, 0xc000);
 347 |     } else {
 348 |       // Load the one bank into both memory locations:
 349 |       this.loadRomBank(0, 0x8000);
 350 |       this.loadRomBank(0, 0xc000);
 351 |     }
 352 |   },
 353 | 
 354 |   loadCHRROM: function () {
 355 |     // console.log("Loading CHR ROM..");
 356 |     if (this.nes.rom.vromCount > 0) {
 357 |       if (this.nes.rom.vromCount === 1) {
 358 |         this.loadVromBank(0, 0x0000);
 359 |         this.loadVromBank(0, 0x1000);
 360 |       } else {
 361 |         this.loadVromBank(0, 0x0000);
 362 |         this.loadVromBank(1, 0x1000);
 363 |       }
 364 |     } else {
 365 |       //System.out.println("There aren't any CHR-ROM banks..");
 366 |     }
 367 |   },
 368 | 
 369 |   loadBatteryRam: function () {
 370 |     if (this.nes.rom.batteryRam) {
 371 |       var ram = this.nes.rom.batteryRam;
 372 |       if (ram !== null && ram.length === 0x2000) {
 373 |         // Load Battery RAM into memory:
 374 |         utils.copyArrayElements(ram, 0, this.nes.cpu.mem, 0x6000, 0x2000);
 375 |       }
 376 |     }
 377 |   },
 378 | 
 379 |   loadRomBank: function (bank, address) {
 380 |     // Loads a ROM bank into the specified address.
 381 |     bank %= this.nes.rom.romCount;
 382 |     //var data = this.nes.rom.rom[bank];
 383 |     //cpuMem.write(address,data,data.length);
 384 |     utils.copyArrayElements(
 385 |       this.nes.rom.rom[bank],
 386 |       0,
 387 |       this.nes.cpu.mem,
 388 |       address,
 389 |       16384
 390 |     );
 391 |   },
 392 | 
 393 |   loadVromBank: function (bank, address) {
 394 |     if (this.nes.rom.vromCount === 0) {
 395 |       return;
 396 |     }
 397 |     this.nes.ppu.triggerRendering();
 398 | 
 399 |     utils.copyArrayElements(
 400 |       this.nes.rom.vrom[bank % this.nes.rom.vromCount],
 401 |       0,
 402 |       this.nes.ppu.vramMem,
 403 |       address,
 404 |       4096
 405 |     );
 406 | 
 407 |     var vromTile = this.nes.rom.vromTile[bank % this.nes.rom.vromCount];
 408 |     utils.copyArrayElements(
 409 |       vromTile,
 410 |       0,
 411 |       this.nes.ppu.ptTile,
 412 |       address >> 4,
 413 |       256
 414 |     );
 415 |   },
 416 | 
 417 |   load32kRomBank: function (bank, address) {
 418 |     this.loadRomBank((bank * 2) % this.nes.rom.romCount, address);
 419 |     this.loadRomBank((bank * 2 + 1) % this.nes.rom.romCount, address + 16384);
 420 |   },
 421 | 
 422 |   load8kVromBank: function (bank4kStart, address) {
 423 |     if (this.nes.rom.vromCount === 0) {
 424 |       return;
 425 |     }
 426 |     this.nes.ppu.triggerRendering();
 427 | 
 428 |     this.loadVromBank(bank4kStart % this.nes.rom.vromCount, address);
 429 |     this.loadVromBank(
 430 |       (bank4kStart + 1) % this.nes.rom.vromCount,
 431 |       address + 4096
 432 |     );
 433 |   },
 434 | 
 435 |   load1kVromBank: function (bank1k, address) {
 436 |     if (this.nes.rom.vromCount === 0) {
 437 |       return;
 438 |     }
 439 |     this.nes.ppu.triggerRendering();
 440 | 
 441 |     var bank4k = Math.floor(bank1k / 4) % this.nes.rom.vromCount;
 442 |     var bankoffset = (bank1k % 4) * 1024;
 443 |     utils.copyArrayElements(
 444 |       this.nes.rom.vrom[bank4k],
 445 |       bankoffset,
 446 |       this.nes.ppu.vramMem,
 447 |       address,
 448 |       1024
 449 |     );
 450 | 
 451 |     // Update tiles:
 452 |     var vromTile = this.nes.rom.vromTile[bank4k];
 453 |     var baseIndex = address >> 4;
 454 |     for (var i = 0; i < 64; i++) {
 455 |       this.nes.ppu.ptTile[baseIndex + i] = vromTile[(bank1k % 4 << 6) + i];
 456 |     }
 457 |   },
 458 | 
 459 |   load2kVromBank: function (bank2k, address) {
 460 |     if (this.nes.rom.vromCount === 0) {
 461 |       return;
 462 |     }
 463 |     this.nes.ppu.triggerRendering();
 464 | 
 465 |     var bank4k = Math.floor(bank2k / 2) % this.nes.rom.vromCount;
 466 |     var bankoffset = (bank2k % 2) * 2048;
 467 |     utils.copyArrayElements(
 468 |       this.nes.rom.vrom[bank4k],
 469 |       bankoffset,
 470 |       this.nes.ppu.vramMem,
 471 |       address,
 472 |       2048
 473 |     );
 474 | 
 475 |     // Update tiles:
 476 |     var vromTile = this.nes.rom.vromTile[bank4k];
 477 |     var baseIndex = address >> 4;
 478 |     for (var i = 0; i < 128; i++) {
 479 |       this.nes.ppu.ptTile[baseIndex + i] = vromTile[(bank2k % 2 << 7) + i];
 480 |     }
 481 |   },
 482 | 
 483 |   load8kRomBank: function (bank8k, address) {
 484 |     var bank16k = Math.floor(bank8k / 2) % this.nes.rom.romCount;
 485 |     var offset = (bank8k % 2) * 8192;
 486 | 
 487 |     //this.nes.cpu.mem.write(address,this.nes.rom.rom[bank16k],offset,8192);
 488 |     utils.copyArrayElements(
 489 |       this.nes.rom.rom[bank16k],
 490 |       offset,
 491 |       this.nes.cpu.mem,
 492 |       address,
 493 |       8192
 494 |     );
 495 |   },
 496 | 
 497 |   clockIrqCounter: function () {
 498 |     // Does nothing. This is used by the MMC3 mapper.
 499 |   },
 500 | 
 501 |   // eslint-disable-next-line no-unused-vars
 502 |   latchAccess: function (address) {
 503 |     // Does nothing. This is used by MMC2.
 504 |   },
 505 | 
 506 |   toJSON: function () {
 507 |     return {
 508 |       joy1StrobeState: this.joy1StrobeState,
 509 |       joy2StrobeState: this.joy2StrobeState,
 510 |       joypadLastWrite: this.joypadLastWrite,
 511 |     };
 512 |   },
 513 | 
 514 |   fromJSON: function (s) {
 515 |     this.joy1StrobeState = s.joy1StrobeState;
 516 |     this.joy2StrobeState = s.joy2StrobeState;
 517 |     this.joypadLastWrite = s.joypadLastWrite;
 518 |   },
 519 | };
 520 | 
 521 | Mappers[1] = function (nes) {
 522 |   this.nes = nes;
 523 | };
 524 | 
 525 | Mappers[1].prototype = new Mappers[0]();
 526 | 
 527 | Mappers[1].prototype.reset = function () {
 528 |   Mappers[0].prototype.reset.apply(this);
 529 | 
 530 |   // 5-bit buffer:
 531 |   this.regBuffer = 0;
 532 |   this.regBufferCounter = 0;
 533 | 
 534 |   // Register 0:
 535 |   this.mirroring = 0;
 536 |   this.oneScreenMirroring = 0;
 537 |   this.prgSwitchingArea = 1;
 538 |   this.prgSwitchingSize = 1;
 539 |   this.vromSwitchingSize = 0;
 540 | 
 541 |   // Register 1:
 542 |   this.romSelectionReg0 = 0;
 543 | 
 544 |   // Register 2:
 545 |   this.romSelectionReg1 = 0;
 546 | 
 547 |   // Register 3:
 548 |   this.romBankSelect = 0;
 549 | };
 550 | 
 551 | Mappers[1].prototype.write = function (address, value) {
 552 |   // Writes to addresses other than MMC registers are handled by NoMapper.
 553 |   if (address < 0x8000) {
 554 |     Mappers[0].prototype.write.apply(this, arguments);
 555 |     return;
 556 |   }
 557 | 
 558 |   // See what should be done with the written value:
 559 |   if ((value & 128) !== 0) {
 560 |     // Reset buffering:
 561 |     this.regBufferCounter = 0;
 562 |     this.regBuffer = 0;
 563 | 
 564 |     // Reset register:
 565 |     if (this.getRegNumber(address) === 0) {
 566 |       this.prgSwitchingArea = 1;
 567 |       this.prgSwitchingSize = 1;
 568 |     }
 569 |   } else {
 570 |     // Continue buffering:
 571 |     //regBuffer = (regBuffer & (0xFF-(1<<regBufferCounter))) | ((value & (1<<regBufferCounter))<<regBufferCounter);
 572 |     this.regBuffer =
 573 |       (this.regBuffer & (0xff - (1 << this.regBufferCounter))) |
 574 |       ((value & 1) << this.regBufferCounter);
 575 |     this.regBufferCounter++;
 576 | 
 577 |     if (this.regBufferCounter === 5) {
 578 |       // Use the buffered value:
 579 |       this.setReg(this.getRegNumber(address), this.regBuffer);
 580 | 
 581 |       // Reset buffer:
 582 |       this.regBuffer = 0;
 583 |       this.regBufferCounter = 0;
 584 |     }
 585 |   }
 586 | };
 587 | 
 588 | Mappers[1].prototype.setReg = function (reg, value) {
 589 |   var tmp;
 590 | 
 591 |   switch (reg) {
 592 |     case 0:
 593 |       // Mirroring:
 594 |       tmp = value & 3;
 595 |       if (tmp !== this.mirroring) {
 596 |         // Set mirroring:
 597 |         this.mirroring = tmp;
 598 |         if ((this.mirroring & 2) === 0) {
 599 |           // SingleScreen mirroring overrides the other setting:
 600 |           this.nes.ppu.setMirroring(this.nes.rom.SINGLESCREEN_MIRRORING);
 601 |         } else if ((this.mirroring & 1) !== 0) {
 602 |           // Not overridden by SingleScreen mirroring.
 603 |           this.nes.ppu.setMirroring(this.nes.rom.HORIZONTAL_MIRRORING);
 604 |         } else {
 605 |           this.nes.ppu.setMirroring(this.nes.rom.VERTICAL_MIRRORING);
 606 |         }
 607 |       }
 608 | 
 609 |       // PRG Switching Area;
 610 |       this.prgSwitchingArea = (value >> 2) & 1;
 611 | 
 612 |       // PRG Switching Size:
 613 |       this.prgSwitchingSize = (value >> 3) & 1;
 614 | 
 615 |       // VROM Switching Size:
 616 |       this.vromSwitchingSize = (value >> 4) & 1;
 617 | 
 618 |       break;
 619 | 
 620 |     case 1:
 621 |       // ROM selection:
 622 |       this.romSelectionReg0 = (value >> 4) & 1;
 623 | 
 624 |       // Check whether the cart has VROM:
 625 |       if (this.nes.rom.vromCount > 0) {
 626 |         // Select VROM bank at 0x0000:
 627 |         if (this.vromSwitchingSize === 0) {
 628 |           // Swap 8kB VROM:
 629 |           if (this.romSelectionReg0 === 0) {
 630 |             this.load8kVromBank(value & 0xf, 0x0000);
 631 |           } else {
 632 |             this.load8kVromBank(
 633 |               Math.floor(this.nes.rom.vromCount / 2) + (value & 0xf),
 634 |               0x0000
 635 |             );
 636 |           }
 637 |         } else {
 638 |           // Swap 4kB VROM:
 639 |           if (this.romSelectionReg0 === 0) {
 640 |             this.loadVromBank(value & 0xf, 0x0000);
 641 |           } else {
 642 |             this.loadVromBank(
 643 |               Math.floor(this.nes.rom.vromCount / 2) + (value & 0xf),
 644 |               0x0000
 645 |             );
 646 |           }
 647 |         }
 648 |       }
 649 | 
 650 |       break;
 651 | 
 652 |     case 2:
 653 |       // ROM selection:
 654 |       this.romSelectionReg1 = (value >> 4) & 1;
 655 | 
 656 |       // Check whether the cart has VROM:
 657 |       if (this.nes.rom.vromCount > 0) {
 658 |         // Select VROM bank at 0x1000:
 659 |         if (this.vromSwitchingSize === 1) {
 660 |           // Swap 4kB of VROM:
 661 |           if (this.romSelectionReg1 === 0) {
 662 |             this.loadVromBank(value & 0xf, 0x1000);
 663 |           } else {
 664 |             this.loadVromBank(
 665 |               Math.floor(this.nes.rom.vromCount / 2) + (value & 0xf),
 666 |               0x1000
 667 |             );
 668 |           }
 669 |         }
 670 |       }
 671 |       break;
 672 | 
 673 |     default:
 674 |       // Select ROM bank:
 675 |       // -------------------------
 676 |       tmp = value & 0xf;
 677 |       var bank;
 678 |       var baseBank = 0;
 679 | 
 680 |       if (this.nes.rom.romCount >= 32) {
 681 |         // 1024 kB cart
 682 |         if (this.vromSwitchingSize === 0) {
 683 |           if (this.romSelectionReg0 === 1) {
 684 |             baseBank = 16;
 685 |           }
 686 |         } else {
 687 |           baseBank =
 688 |             (this.romSelectionReg0 | (this.romSelectionReg1 << 1)) << 3;
 689 |         }
 690 |       } else if (this.nes.rom.romCount >= 16) {
 691 |         // 512 kB cart
 692 |         if (this.romSelectionReg0 === 1) {
 693 |           baseBank = 8;
 694 |         }
 695 |       }
 696 | 
 697 |       if (this.prgSwitchingSize === 0) {
 698 |         // 32kB
 699 |         bank = baseBank + (value & 0xf);
 700 |         this.load32kRomBank(bank, 0x8000);
 701 |       } else {
 702 |         // 16kB
 703 |         bank = baseBank * 2 + (value & 0xf);
 704 |         if (this.prgSwitchingArea === 0) {
 705 |           this.loadRomBank(bank, 0xc000);
 706 |         } else {
 707 |           this.loadRomBank(bank, 0x8000);
 708 |         }
 709 |       }
 710 |   }
 711 | };
 712 | 
 713 | // Returns the register number from the address written to:
 714 | Mappers[1].prototype.getRegNumber = function (address) {
 715 |   if (address >= 0x8000 && address <= 0x9fff) {
 716 |     return 0;
 717 |   } else if (address >= 0xa000 && address <= 0xbfff) {
 718 |     return 1;
 719 |   } else if (address >= 0xc000 && address <= 0xdfff) {
 720 |     return 2;
 721 |   } else {
 722 |     return 3;
 723 |   }
 724 | };
 725 | 
 726 | Mappers[1].prototype.loadROM = function () {
 727 |   if (!this.nes.rom.valid) {
 728 |     throw new Error("MMC1: Invalid ROM! Unable to load.");
 729 |   }
 730 | 
 731 |   // Load PRG-ROM:
 732 |   this.loadRomBank(0, 0x8000); //   First ROM bank..
 733 |   this.loadRomBank(this.nes.rom.romCount - 1, 0xc000); // ..and last ROM bank.
 734 | 
 735 |   // Load CHR-ROM:
 736 |   this.loadCHRROM();
 737 | 
 738 |   // Load Battery RAM (if present):
 739 |   this.loadBatteryRam();
 740 | 
 741 |   // Do Reset-Interrupt:
 742 |   this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
 743 | };
 744 | 
 745 | // eslint-disable-next-line no-unused-vars
 746 | Mappers[1].prototype.switchLowHighPrgRom = function (oldSetting) {
 747 |   // not yet.
 748 | };
 749 | 
 750 | Mappers[1].prototype.switch16to32 = function () {
 751 |   // not yet.
 752 | };
 753 | 
 754 | Mappers[1].prototype.switch32to16 = function () {
 755 |   // not yet.
 756 | };
 757 | 
 758 | Mappers[1].prototype.toJSON = function () {
 759 |   var s = Mappers[0].prototype.toJSON.apply(this);
 760 |   s.mirroring = this.mirroring;
 761 |   s.oneScreenMirroring = this.oneScreenMirroring;
 762 |   s.prgSwitchingArea = this.prgSwitchingArea;
 763 |   s.prgSwitchingSize = this.prgSwitchingSize;
 764 |   s.vromSwitchingSize = this.vromSwitchingSize;
 765 |   s.romSelectionReg0 = this.romSelectionReg0;
 766 |   s.romSelectionReg1 = this.romSelectionReg1;
 767 |   s.romBankSelect = this.romBankSelect;
 768 |   s.regBuffer = this.regBuffer;
 769 |   s.regBufferCounter = this.regBufferCounter;
 770 |   return s;
 771 | };
 772 | 
 773 | Mappers[1].prototype.fromJSON = function (s) {
 774 |   Mappers[0].prototype.fromJSON.apply(this, arguments);
 775 |   this.mirroring = s.mirroring;
 776 |   this.oneScreenMirroring = s.oneScreenMirroring;
 777 |   this.prgSwitchingArea = s.prgSwitchingArea;
 778 |   this.prgSwitchingSize = s.prgSwitchingSize;
 779 |   this.vromSwitchingSize = s.vromSwitchingSize;
 780 |   this.romSelectionReg0 = s.romSelectionReg0;
 781 |   this.romSelectionReg1 = s.romSelectionReg1;
 782 |   this.romBankSelect = s.romBankSelect;
 783 |   this.regBuffer = s.regBuffer;
 784 |   this.regBufferCounter = s.regBufferCounter;
 785 | };
 786 | 
 787 | Mappers[2] = function (nes) {
 788 |   this.nes = nes;
 789 | };
 790 | 
 791 | Mappers[2].prototype = new Mappers[0]();
 792 | 
 793 | Mappers[2].prototype.write = function (address, value) {
 794 |   // Writes to addresses other than MMC registers are handled by NoMapper.
 795 |   if (address < 0x8000) {
 796 |     Mappers[0].prototype.write.apply(this, arguments);
 797 |     return;
 798 |   } else {
 799 |     // This is a ROM bank select command.
 800 |     // Swap in the given ROM bank at 0x8000:
 801 |     this.loadRomBank(value, 0x8000);
 802 |   }
 803 | };
 804 | 
 805 | Mappers[2].prototype.loadROM = function () {
 806 |   if (!this.nes.rom.valid) {
 807 |     throw new Error("UNROM: Invalid ROM! Unable to load.");
 808 |   }
 809 | 
 810 |   // Load PRG-ROM:
 811 |   this.loadRomBank(0, 0x8000);
 812 |   this.loadRomBank(this.nes.rom.romCount - 1, 0xc000);
 813 | 
 814 |   // Load CHR-ROM:
 815 |   this.loadCHRROM();
 816 | 
 817 |   // Do Reset-Interrupt:
 818 |   this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
 819 | };
 820 | 
 821 | /**
 822 |  * Mapper 003 (CNROM)
 823 |  *
 824 |  * @constructor
 825 |  * @example Solomon's Key, Arkanoid, Arkista's Ring, Bump 'n' Jump, Cybernoid
 826 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_003
 827 |  */
 828 | Mappers[3] = function (nes) {
 829 |   this.nes = nes;
 830 | };
 831 | 
 832 | Mappers[3].prototype = new Mappers[0]();
 833 | 
 834 | Mappers[3].prototype.write = function (address, value) {
 835 |   // Writes to addresses other than MMC registers are handled by NoMapper.
 836 |   if (address < 0x8000) {
 837 |     Mappers[0].prototype.write.apply(this, arguments);
 838 |     return;
 839 |   } else {
 840 |     // This is a ROM bank select command.
 841 |     // Swap in the given ROM bank at 0x8000:
 842 |     // This is a VROM bank select command.
 843 |     // Swap in the given VROM bank at 0x0000:
 844 |     var bank = (value % (this.nes.rom.vromCount / 2)) * 2;
 845 |     this.loadVromBank(bank, 0x0000);
 846 |     this.loadVromBank(bank + 1, 0x1000);
 847 |     this.load8kVromBank(value * 2, 0x0000);
 848 |   }
 849 | };
 850 | 
 851 | Mappers[4] = function (nes) {
 852 |   this.nes = nes;
 853 | 
 854 |   this.CMD_SEL_2_1K_VROM_0000 = 0;
 855 |   this.CMD_SEL_2_1K_VROM_0800 = 1;
 856 |   this.CMD_SEL_1K_VROM_1000 = 2;
 857 |   this.CMD_SEL_1K_VROM_1400 = 3;
 858 |   this.CMD_SEL_1K_VROM_1800 = 4;
 859 |   this.CMD_SEL_1K_VROM_1C00 = 5;
 860 |   this.CMD_SEL_ROM_PAGE1 = 6;
 861 |   this.CMD_SEL_ROM_PAGE2 = 7;
 862 | 
 863 |   this.command = null;
 864 |   this.prgAddressSelect = null;
 865 |   this.chrAddressSelect = null;
 866 |   this.pageNumber = null;
 867 |   this.irqCounter = null;
 868 |   this.irqLatchValue = null;
 869 |   this.irqEnable = null;
 870 |   this.prgAddressChanged = false;
 871 | };
 872 | 
 873 | Mappers[4].prototype = new Mappers[0]();
 874 | 
 875 | Mappers[4].prototype.write = function (address, value) {
 876 |   // Writes to addresses other than MMC registers are handled by NoMapper.
 877 |   if (address < 0x8000) {
 878 |     Mappers[0].prototype.write.apply(this, arguments);
 879 |     return;
 880 |   }
 881 | 
 882 |   switch (address) {
 883 |     case 0x8000:
 884 |       // Command/Address Select register
 885 |       this.command = value & 7;
 886 |       var tmp = (value >> 6) & 1;
 887 |       if (tmp !== this.prgAddressSelect) {
 888 |         this.prgAddressChanged = true;
 889 |       }
 890 |       this.prgAddressSelect = tmp;
 891 |       this.chrAddressSelect = (value >> 7) & 1;
 892 |       break;
 893 | 
 894 |     case 0x8001:
 895 |       // Page number for command
 896 |       this.executeCommand(this.command, value);
 897 |       break;
 898 | 
 899 |     case 0xa000:
 900 |       // Mirroring select
 901 |       if ((value & 1) !== 0) {
 902 |         this.nes.ppu.setMirroring(this.nes.rom.HORIZONTAL_MIRRORING);
 903 |       } else {
 904 |         this.nes.ppu.setMirroring(this.nes.rom.VERTICAL_MIRRORING);
 905 |       }
 906 |       break;
 907 | 
 908 |     case 0xa001:
 909 |       // SaveRAM Toggle
 910 |       // TODO
 911 |       //nes.getRom().setSaveState((value&1)!=0);
 912 |       break;
 913 | 
 914 |     case 0xc000:
 915 |       // IRQ Counter register
 916 |       this.irqCounter = value;
 917 |       //nes.ppu.mapperIrqCounter = 0;
 918 |       break;
 919 | 
 920 |     case 0xc001:
 921 |       // IRQ Latch register
 922 |       this.irqLatchValue = value;
 923 |       break;
 924 | 
 925 |     case 0xe000:
 926 |       // IRQ Control Reg 0 (disable)
 927 |       //irqCounter = irqLatchValue;
 928 |       this.irqEnable = 0;
 929 |       break;
 930 | 
 931 |     case 0xe001:
 932 |       // IRQ Control Reg 1 (enable)
 933 |       this.irqEnable = 1;
 934 |       break;
 935 | 
 936 |     default:
 937 |     // Not a MMC3 register.
 938 |     // The game has probably crashed,
 939 |     // since it tries to write to ROM..
 940 |     // IGNORE.
 941 |   }
 942 | };
 943 | 
 944 | Mappers[4].prototype.executeCommand = function (cmd, arg) {
 945 |   switch (cmd) {
 946 |     case this.CMD_SEL_2_1K_VROM_0000:
 947 |       // Select 2 1KB VROM pages at 0x0000:
 948 |       if (this.chrAddressSelect === 0) {
 949 |         this.load1kVromBank(arg, 0x0000);
 950 |         this.load1kVromBank(arg + 1, 0x0400);
 951 |       } else {
 952 |         this.load1kVromBank(arg, 0x1000);
 953 |         this.load1kVromBank(arg + 1, 0x1400);
 954 |       }
 955 |       break;
 956 | 
 957 |     case this.CMD_SEL_2_1K_VROM_0800:
 958 |       // Select 2 1KB VROM pages at 0x0800:
 959 |       if (this.chrAddressSelect === 0) {
 960 |         this.load1kVromBank(arg, 0x0800);
 961 |         this.load1kVromBank(arg + 1, 0x0c00);
 962 |       } else {
 963 |         this.load1kVromBank(arg, 0x1800);
 964 |         this.load1kVromBank(arg + 1, 0x1c00);
 965 |       }
 966 |       break;
 967 | 
 968 |     case this.CMD_SEL_1K_VROM_1000:
 969 |       // Select 1K VROM Page at 0x1000:
 970 |       if (this.chrAddressSelect === 0) {
 971 |         this.load1kVromBank(arg, 0x1000);
 972 |       } else {
 973 |         this.load1kVromBank(arg, 0x0000);
 974 |       }
 975 |       break;
 976 | 
 977 |     case this.CMD_SEL_1K_VROM_1400:
 978 |       // Select 1K VROM Page at 0x1400:
 979 |       if (this.chrAddressSelect === 0) {
 980 |         this.load1kVromBank(arg, 0x1400);
 981 |       } else {
 982 |         this.load1kVromBank(arg, 0x0400);
 983 |       }
 984 |       break;
 985 | 
 986 |     case this.CMD_SEL_1K_VROM_1800:
 987 |       // Select 1K VROM Page at 0x1800:
 988 |       if (this.chrAddressSelect === 0) {
 989 |         this.load1kVromBank(arg, 0x1800);
 990 |       } else {
 991 |         this.load1kVromBank(arg, 0x0800);
 992 |       }
 993 |       break;
 994 | 
 995 |     case this.CMD_SEL_1K_VROM_1C00:
 996 |       // Select 1K VROM Page at 0x1C00:
 997 |       if (this.chrAddressSelect === 0) {
 998 |         this.load1kVromBank(arg, 0x1c00);
 999 |       } else {
1000 |         this.load1kVromBank(arg, 0x0c00);
1001 |       }
1002 |       break;
1003 | 
1004 |     case this.CMD_SEL_ROM_PAGE1:
1005 |       if (this.prgAddressChanged) {
1006 |         // Load the two hardwired banks:
1007 |         if (this.prgAddressSelect === 0) {
1008 |           this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0xc000);
1009 |         } else {
1010 |           this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0x8000);
1011 |         }
1012 |         this.prgAddressChanged = false;
1013 |       }
1014 | 
1015 |       // Select first switchable ROM page:
1016 |       if (this.prgAddressSelect === 0) {
1017 |         this.load8kRomBank(arg, 0x8000);
1018 |       } else {
1019 |         this.load8kRomBank(arg, 0xc000);
1020 |       }
1021 |       break;
1022 | 
1023 |     case this.CMD_SEL_ROM_PAGE2:
1024 |       // Select second switchable ROM page:
1025 |       this.load8kRomBank(arg, 0xa000);
1026 | 
1027 |       // hardwire appropriate bank:
1028 |       if (this.prgAddressChanged) {
1029 |         // Load the two hardwired banks:
1030 |         if (this.prgAddressSelect === 0) {
1031 |           this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0xc000);
1032 |         } else {
1033 |           this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0x8000);
1034 |         }
1035 |         this.prgAddressChanged = false;
1036 |       }
1037 |   }
1038 | };
1039 | 
1040 | Mappers[4].prototype.loadROM = function () {
1041 |   if (!this.nes.rom.valid) {
1042 |     throw new Error("MMC3: Invalid ROM! Unable to load.");
1043 |   }
1044 | 
1045 |   // Load hardwired PRG banks (0xC000 and 0xE000):
1046 |   this.load8kRomBank((this.nes.rom.romCount - 1) * 2, 0xc000);
1047 |   this.load8kRomBank((this.nes.rom.romCount - 1) * 2 + 1, 0xe000);
1048 | 
1049 |   // Load swappable PRG banks (0x8000 and 0xA000):
1050 |   this.load8kRomBank(0, 0x8000);
1051 |   this.load8kRomBank(1, 0xa000);
1052 | 
1053 |   // Load CHR-ROM:
1054 |   this.loadCHRROM();
1055 | 
1056 |   // Load Battery RAM (if present):
1057 |   this.loadBatteryRam();
1058 | 
1059 |   // Do Reset-Interrupt:
1060 |   this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
1061 | };
1062 | 
1063 | Mappers[4].prototype.clockIrqCounter = function () {
1064 |   if (this.irqEnable === 1) {
1065 |     this.irqCounter--;
1066 |     if (this.irqCounter < 0) {
1067 |       // Trigger IRQ:
1068 |       //nes.getCpu().doIrq();
1069 |       this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NORMAL);
1070 |       this.irqCounter = this.irqLatchValue;
1071 |     }
1072 |   }
1073 | };
1074 | 
1075 | Mappers[4].prototype.toJSON = function () {
1076 |   var s = Mappers[0].prototype.toJSON.apply(this);
1077 |   s.command = this.command;
1078 |   s.prgAddressSelect = this.prgAddressSelect;
1079 |   s.chrAddressSelect = this.chrAddressSelect;
1080 |   s.pageNumber = this.pageNumber;
1081 |   s.irqCounter = this.irqCounter;
1082 |   s.irqLatchValue = this.irqLatchValue;
1083 |   s.irqEnable = this.irqEnable;
1084 |   s.prgAddressChanged = this.prgAddressChanged;
1085 |   return s;
1086 | };
1087 | 
1088 | Mappers[4].prototype.fromJSON = function (s) {
1089 |   Mappers[0].prototype.fromJSON.apply(this, arguments);
1090 |   this.command = s.command;
1091 |   this.prgAddressSelect = s.prgAddressSelect;
1092 |   this.chrAddressSelect = s.chrAddressSelect;
1093 |   this.pageNumber = s.pageNumber;
1094 |   this.irqCounter = s.irqCounter;
1095 |   this.irqLatchValue = s.irqLatchValue;
1096 |   this.irqEnable = s.irqEnable;
1097 |   this.prgAddressChanged = s.prgAddressChanged;
1098 | };
1099 | 
1100 | /**
1101 |  * Mapper005 (MMC5,ExROM)
1102 |  *
1103 |  * @example Castlevania 3, Just Breed, Uncharted Waters, Romance of the 3 Kingdoms 2, Laser Invasion, Metal Slader Glory, Uchuu Keibitai SDF, Shin 4 Nin Uchi Mahjong - Yakuman Tengoku
1104 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_005
1105 |  * @constructor
1106 |  */
1107 | Mappers[5] = function (nes) {
1108 |   this.nes = nes;
1109 | };
1110 | 
1111 | Mappers[5].prototype = new Mappers[0]();
1112 | 
1113 | Mappers[5].prototype.write = function (address, value) {
1114 |   // Writes to addresses other than MMC registers are handled by NoMapper.
1115 |   if (address < 0x8000) {
1116 |     Mappers[0].prototype.write.apply(this, arguments);
1117 |   } else {
1118 |     this.load8kVromBank(value, 0x0000);
1119 |   }
1120 | };
1121 | 
1122 | Mappers[5].prototype.write = function (address, value) {
1123 |   // Writes to addresses other than MMC registers are handled by NoMapper.
1124 |   if (address < 0x5000) {
1125 |     Mappers[0].prototype.write.apply(this, arguments);
1126 |     return;
1127 |   }
1128 | 
1129 |   switch (address) {
1130 |     case 0x5100:
1131 |       this.prg_size = value & 3;
1132 |       break;
1133 |     case 0x5101:
1134 |       this.chr_size = value & 3;
1135 |       break;
1136 |     case 0x5102:
1137 |       this.sram_we_a = value & 3;
1138 |       break;
1139 |     case 0x5103:
1140 |       this.sram_we_b = value & 3;
1141 |       break;
1142 |     case 0x5104:
1143 |       this.graphic_mode = value & 3;
1144 |       break;
1145 |     case 0x5105:
1146 |       this.nametable_mode = value;
1147 |       this.nametable_type[0] = value & 3;
1148 |       this.load1kVromBank(value & 3, 0x2000);
1149 |       value >>= 2;
1150 |       this.nametable_type[1] = value & 3;
1151 |       this.load1kVromBank(value & 3, 0x2400);
1152 |       value >>= 2;
1153 |       this.nametable_type[2] = value & 3;
1154 |       this.load1kVromBank(value & 3, 0x2800);
1155 |       value >>= 2;
1156 |       this.nametable_type[3] = value & 3;
1157 |       this.load1kVromBank(value & 3, 0x2c00);
1158 |       break;
1159 |     case 0x5106:
1160 |       this.fill_chr = value;
1161 |       break;
1162 |     case 0x5107:
1163 |       this.fill_pal = value & 3;
1164 |       break;
1165 |     case 0x5113:
1166 |       this.SetBank_SRAM(3, value & 3);
1167 |       break;
1168 |     case 0x5114:
1169 |     case 0x5115:
1170 |     case 0x5116:
1171 |     case 0x5117:
1172 |       this.SetBank_CPU(address, value);
1173 |       break;
1174 |     case 0x5120:
1175 |     case 0x5121:
1176 |     case 0x5122:
1177 |     case 0x5123:
1178 |     case 0x5124:
1179 |     case 0x5125:
1180 |     case 0x5126:
1181 |     case 0x5127:
1182 |       this.chr_mode = 0;
1183 |       this.chr_page[0][address & 7] = value;
1184 |       this.SetBank_PPU();
1185 |       break;
1186 |     case 0x5128:
1187 |     case 0x5129:
1188 |     case 0x512a:
1189 |     case 0x512b:
1190 |       this.chr_mode = 1;
1191 |       this.chr_page[1][(address & 3) + 0] = value;
1192 |       this.chr_page[1][(address & 3) + 4] = value;
1193 |       this.SetBank_PPU();
1194 |       break;
1195 |     case 0x5200:
1196 |       this.split_control = value;
1197 |       break;
1198 |     case 0x5201:
1199 |       this.split_scroll = value;
1200 |       break;
1201 |     case 0x5202:
1202 |       this.split_page = value & 0x3f;
1203 |       break;
1204 |     case 0x5203:
1205 |       this.irq_line = value;
1206 |       this.nes.cpu.ClearIRQ();
1207 |       break;
1208 |     case 0x5204:
1209 |       this.irq_enable = value;
1210 |       this.nes.cpu.ClearIRQ();
1211 |       break;
1212 |     case 0x5205:
1213 |       this.mult_a = value;
1214 |       break;
1215 |     case 0x5206:
1216 |       this.mult_b = value;
1217 |       break;
1218 |     default:
1219 |       if (address >= 0x5000 && address <= 0x5015) {
1220 |         this.nes.papu.exWrite(address, value);
1221 |       } else if (address >= 0x5c00 && address <= 0x5fff) {
1222 |         if (this.graphic_mode === 2) {
1223 |           // ExRAM
1224 |           // vram write
1225 |         } else if (this.graphic_mode !== 3) {
1226 |           // Split,ExGraphic
1227 |           if (this.irq_status & 0x40) {
1228 |             // vram write
1229 |           } else {
1230 |             // vram write
1231 |           }
1232 |         }
1233 |       } else if (address >= 0x6000 && address <= 0x7fff) {
1234 |         if (this.sram_we_a === 2 && this.sram_we_b === 1) {
1235 |           // additional ram write
1236 |         }
1237 |       }
1238 |       break;
1239 |   }
1240 | };
1241 | 
1242 | Mappers[5].prototype.loadROM = function () {
1243 |   if (!this.nes.rom.valid) {
1244 |     throw new Error("UNROM: Invalid ROM! Unable to load.");
1245 |   }
1246 | 
1247 |   // Load PRG-ROM:
1248 |   this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0x8000);
1249 |   this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xa000);
1250 |   this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xc000);
1251 |   this.load8kRomBank(this.nes.rom.romCount * 2 - 1, 0xe000);
1252 | 
1253 |   // Load CHR-ROM:
1254 |   this.loadCHRROM();
1255 | 
1256 |   // Do Reset-Interrupt:
1257 |   this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
1258 | };
1259 | 
1260 | /**
1261 |  * Mapper007 (AxROM)
1262 |  * @example Battletoads, Time Lord, Marble Madness
1263 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_007
1264 |  * @constructor
1265 |  */
1266 | Mappers[7] = function (nes) {
1267 |   this.nes = nes;
1268 | };
1269 | 
1270 | Mappers[7].prototype = new Mappers[0]();
1271 | 
1272 | Mappers[7].prototype.write = function (address, value) {
1273 |   // Writes to addresses other than MMC registers are handled by NoMapper.
1274 |   if (address < 0x8000) {
1275 |     Mappers[0].prototype.write.apply(this, arguments);
1276 |   } else {
1277 |     this.load32kRomBank(value & 0x7, 0x8000);
1278 |     if (value & 0x10) {
1279 |       this.nes.ppu.setMirroring(this.nes.rom.SINGLESCREEN_MIRRORING2);
1280 |     } else {
1281 |       this.nes.ppu.setMirroring(this.nes.rom.SINGLESCREEN_MIRRORING);
1282 |     }
1283 |   }
1284 | };
1285 | 
1286 | Mappers[7].prototype.loadROM = function () {
1287 |   if (!this.nes.rom.valid) {
1288 |     throw new Error("AOROM: Invalid ROM! Unable to load.");
1289 |   }
1290 | 
1291 |   // Load PRG-ROM:
1292 |   this.loadPRGROM();
1293 | 
1294 |   // Load CHR-ROM:
1295 |   this.loadCHRROM();
1296 | 
1297 |   // Do Reset-Interrupt:
1298 |   this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
1299 | };
1300 | 
1301 | /**
1302 |  * Mapper 011 (Color Dreams)
1303 |  *
1304 |  * @description http://wiki.nesdev.com/w/index.php/Color_Dreams
1305 |  * @example Crystal Mines, Metal Fighter
1306 |  * @constructor
1307 |  */
1308 | Mappers[11] = function (nes) {
1309 |   this.nes = nes;
1310 | };
1311 | 
1312 | Mappers[11].prototype = new Mappers[0]();
1313 | 
1314 | Mappers[11].prototype.write = function (address, value) {
1315 |   if (address < 0x8000) {
1316 |     Mappers[0].prototype.write.apply(this, arguments);
1317 |     return;
1318 |   } else {
1319 |     // Swap in the given PRG-ROM bank:
1320 |     var prgbank1 = ((value & 0xf) * 2) % this.nes.rom.romCount;
1321 |     var prgbank2 = ((value & 0xf) * 2 + 1) % this.nes.rom.romCount;
1322 | 
1323 |     this.loadRomBank(prgbank1, 0x8000);
1324 |     this.loadRomBank(prgbank2, 0xc000);
1325 | 
1326 |     if (this.nes.rom.vromCount > 0) {
1327 |       // Swap in the given VROM bank at 0x0000:
1328 |       var bank = ((value >> 4) * 2) % this.nes.rom.vromCount;
1329 |       this.loadVromBank(bank, 0x0000);
1330 |       this.loadVromBank(bank + 1, 0x1000);
1331 |     }
1332 |   }
1333 | };
1334 | 
1335 | /**
1336 |  * Mapper 034 (BNROM, NINA-01)
1337 |  *
1338 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_034
1339 |  * @example Darkseed, Mashou, Mission Impossible 2
1340 |  * @constructor
1341 |  */
1342 | Mappers[34] = function (nes) {
1343 |   this.nes = nes;
1344 | };
1345 | 
1346 | Mappers[34].prototype = new Mappers[0]();
1347 | 
1348 | Mappers[34].prototype.write = function (address, value) {
1349 |   if (address < 0x8000) {
1350 |     Mappers[0].prototype.write.apply(this, arguments);
1351 |     return;
1352 |   } else {
1353 |     this.load32kRomBank(value, 0x8000);
1354 |   }
1355 | };
1356 | 
1357 | /**
1358 |  * Mapper 038
1359 |  *
1360 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_038
1361 |  * @example Crime Busters
1362 |  * @constructor
1363 |  */
1364 | Mappers[38] = function (nes) {
1365 |   this.nes = nes;
1366 | };
1367 | 
1368 | Mappers[38].prototype = new Mappers[0]();
1369 | 
1370 | Mappers[38].prototype.write = function (address, value) {
1371 |   if (address < 0x7000 || address > 0x7fff) {
1372 |     Mappers[0].prototype.write.apply(this, arguments);
1373 |     return;
1374 |   } else {
1375 |     // Swap in the given PRG-ROM bank at 0x8000:
1376 |     this.load32kRomBank(value & 3, 0x8000);
1377 | 
1378 |     // Swap in the given VROM bank at 0x0000:
1379 |     this.load8kVromBank(((value >> 2) & 3) * 2, 0x0000);
1380 |   }
1381 | };
1382 | 
1383 | /**
1384 |  * Mapper 066 (GxROM)
1385 |  *
1386 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_066
1387 |  * @example Doraemon, Dragon Power, Gumshoe, Thunder & Lightning,
1388 |  * Super Mario Bros. + Duck Hunt
1389 |  * @constructor
1390 |  */
1391 | Mappers[66] = function (nes) {
1392 |   this.nes = nes;
1393 | };
1394 | 
1395 | Mappers[66].prototype = new Mappers[0]();
1396 | 
1397 | Mappers[66].prototype.write = function (address, value) {
1398 |   if (address < 0x8000) {
1399 |     Mappers[0].prototype.write.apply(this, arguments);
1400 |     return;
1401 |   } else {
1402 |     // Swap in the given PRG-ROM bank at 0x8000:
1403 |     this.load32kRomBank((value >> 4) & 3, 0x8000);
1404 | 
1405 |     // Swap in the given VROM bank at 0x0000:
1406 |     this.load8kVromBank((value & 3) * 2, 0x0000);
1407 |   }
1408 | };
1409 | 
1410 | /**
1411 |  * Mapper 094 (UN1ROM)
1412 |  *
1413 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_094
1414 |  * @example Senjou no Ookami
1415 |  * @constructor
1416 |  */
1417 | Mappers[94] = function (nes) {
1418 |   this.nes = nes;
1419 | };
1420 | 
1421 | Mappers[94].prototype = new Mappers[0]();
1422 | 
1423 | Mappers[94].prototype.write = function (address, value) {
1424 |   // Writes to addresses other than MMC registers are handled by NoMapper.
1425 |   if (address < 0x8000) {
1426 |     Mappers[0].prototype.write.apply(this, arguments);
1427 |     return;
1428 |   } else {
1429 |     // This is a ROM bank select command.
1430 |     // Swap in the given ROM bank at 0x8000:
1431 |     this.loadRomBank(value >> 2, 0x8000);
1432 |   }
1433 | };
1434 | 
1435 | Mappers[94].prototype.loadROM = function () {
1436 |   if (!this.nes.rom.valid) {
1437 |     throw new Error("UN1ROM: Invalid ROM! Unable to load.");
1438 |   }
1439 | 
1440 |   // Load PRG-ROM:
1441 |   this.loadRomBank(0, 0x8000);
1442 |   this.loadRomBank(this.nes.rom.romCount - 1, 0xc000);
1443 | 
1444 |   // Load CHR-ROM:
1445 |   this.loadCHRROM();
1446 | 
1447 |   // Do Reset-Interrupt:
1448 |   this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
1449 | };
1450 | 
1451 | /**
1452 |  * Mapper 140
1453 |  *
1454 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_140
1455 |  * @example Bio Senshi Dan - Increaser Tono Tatakai
1456 |  * @constructor
1457 |  */
1458 | Mappers[140] = function (nes) {
1459 |   this.nes = nes;
1460 | };
1461 | 
1462 | Mappers[140].prototype = new Mappers[0]();
1463 | 
1464 | Mappers[140].prototype.write = function (address, value) {
1465 |   if (address < 0x6000 || address > 0x7fff) {
1466 |     Mappers[0].prototype.write.apply(this, arguments);
1467 |     return;
1468 |   } else {
1469 |     // Swap in the given PRG-ROM bank at 0x8000:
1470 |     this.load32kRomBank((value >> 4) & 3, 0x8000);
1471 | 
1472 |     // Swap in the given VROM bank at 0x0000:
1473 |     this.load8kVromBank((value & 0xf) * 2, 0x0000);
1474 |   }
1475 | };
1476 | 
1477 | /**
1478 |  * Mapper 180
1479 |  *
1480 |  * @description http://wiki.nesdev.com/w/index.php/INES_Mapper_180
1481 |  * @example Crazy Climber
1482 |  * @constructor
1483 |  */
1484 | Mappers[180] = function (nes) {
1485 |   this.nes = nes;
1486 | };
1487 | 
1488 | Mappers[180].prototype = new Mappers[0]();
1489 | 
1490 | Mappers[180].prototype.write = function (address, value) {
1491 |   // Writes to addresses other than MMC registers are handled by NoMapper.
1492 |   if (address < 0x8000) {
1493 |     Mappers[0].prototype.write.apply(this, arguments);
1494 |     return;
1495 |   } else {
1496 |     // This is a ROM bank select command.
1497 |     // Swap in the given ROM bank at 0xc000:
1498 |     this.loadRomBank(value, 0xc000);
1499 |   }
1500 | };
1501 | 
1502 | Mappers[180].prototype.loadROM = function () {
1503 |   if (!this.nes.rom.valid) {
1504 |     throw new Error("Mapper 180: Invalid ROM! Unable to load.");
1505 |   }
1506 | 
1507 |   // Load PRG-ROM:
1508 |   this.loadRomBank(0, 0x8000);
1509 |   this.loadRomBank(this.nes.rom.romCount - 1, 0xc000);
1510 | 
1511 |   // Load CHR-ROM:
1512 |   this.loadCHRROM();
1513 | 
1514 |   // Do Reset-Interrupt:
1515 |   this.nes.cpu.requestIrq(this.nes.cpu.IRQ_RESET);
1516 | };
1517 | 
1518 | module.exports = Mappers;
1519 | 


--------------------------------------------------------------------------------
/src/nes.js:
--------------------------------------------------------------------------------
  1 | var CPU = require("./cpu");
  2 | var Controller = require("./controller");
  3 | var PPU = require("./ppu");
  4 | var PAPU = require("./papu");
  5 | var ROM = require("./rom");
  6 | 
  7 | var NES = function (opts) {
  8 |   this.opts = {
  9 |     onFrame: function () {},
 10 |     onAudioSample: null,
 11 |     onStatusUpdate: function () {},
 12 |     onBatteryRamWrite: function () {},
 13 | 
 14 |     // FIXME: not actually used except for in PAPU
 15 |     preferredFrameRate: 60,
 16 | 
 17 |     emulateSound: true,
 18 |     sampleRate: 48000, // Sound sample rate in hz
 19 |   };
 20 |   if (typeof opts !== "undefined") {
 21 |     var key;
 22 |     for (key in this.opts) {
 23 |       if (typeof opts[key] !== "undefined") {
 24 |         this.opts[key] = opts[key];
 25 |       }
 26 |     }
 27 |   }
 28 | 
 29 |   this.frameTime = 1000 / this.opts.preferredFrameRate;
 30 | 
 31 |   this.ui = {
 32 |     writeFrame: this.opts.onFrame,
 33 |     updateStatus: this.opts.onStatusUpdate,
 34 |   };
 35 |   this.cpu = new CPU(this);
 36 |   this.ppu = new PPU(this);
 37 |   this.papu = new PAPU(this);
 38 |   this.mmap = null; // set in loadROM()
 39 |   this.controllers = {
 40 |     1: new Controller(),
 41 |     2: new Controller(),
 42 |   };
 43 | 
 44 |   this.ui.updateStatus("Ready to load a ROM.");
 45 | 
 46 |   this.frame = this.frame.bind(this);
 47 |   this.buttonDown = this.buttonDown.bind(this);
 48 |   this.buttonUp = this.buttonUp.bind(this);
 49 |   this.zapperMove = this.zapperMove.bind(this);
 50 |   this.zapperFireDown = this.zapperFireDown.bind(this);
 51 |   this.zapperFireUp = this.zapperFireUp.bind(this);
 52 | };
 53 | 
 54 | NES.prototype = {
 55 |   fpsFrameCount: 0,
 56 |   romData: null,
 57 |   break: false,
 58 | 
 59 |   // Set break to true to stop frame loop.
 60 |   stop: function () {
 61 |     this.break = true;
 62 |   },
 63 | 
 64 |   // Resets the system
 65 |   reset: function () {
 66 |     if (this.mmap !== null) {
 67 |       this.mmap.reset();
 68 |     }
 69 | 
 70 |     this.cpu.reset();
 71 |     this.ppu.reset();
 72 |     this.papu.reset();
 73 | 
 74 |     this.lastFpsTime = null;
 75 |     this.fpsFrameCount = 0;
 76 | 
 77 |     this.break = false;
 78 |   },
 79 | 
 80 |   frame: function () {
 81 |     this.ppu.startFrame();
 82 |     var cycles = 0;
 83 |     var emulateSound = this.opts.emulateSound;
 84 |     var cpu = this.cpu;
 85 |     var ppu = this.ppu;
 86 |     var papu = this.papu;
 87 |     FRAMELOOP: for (;;) {
 88 |       if (this.break) break;
 89 |       if (cpu.cyclesToHalt === 0) {
 90 |         // Execute a CPU instruction
 91 |         cycles = cpu.emulate();
 92 |         if (emulateSound) {
 93 |           papu.clockFrameCounter(cycles);
 94 |         }
 95 |         cycles *= 3;
 96 |       } else {
 97 |         if (cpu.cyclesToHalt > 8) {
 98 |           cycles = 24;
 99 |           if (emulateSound) {
100 |             papu.clockFrameCounter(8);
101 |           }
102 |           cpu.cyclesToHalt -= 8;
103 |         } else {
104 |           cycles = cpu.cyclesToHalt * 3;
105 |           if (emulateSound) {
106 |             papu.clockFrameCounter(cpu.cyclesToHalt);
107 |           }
108 |           cpu.cyclesToHalt = 0;
109 |         }
110 |       }
111 | 
112 |       for (; cycles > 0; cycles--) {
113 |         if (
114 |           ppu.curX === ppu.spr0HitX &&
115 |           ppu.f_spVisibility === 1 &&
116 |           ppu.scanline - 21 === ppu.spr0HitY
117 |         ) {
118 |           // Set sprite 0 hit flag:
119 |           ppu.setStatusFlag(ppu.STATUS_SPRITE0HIT, true);
120 |         }
121 | 
122 |         if (ppu.requestEndFrame) {
123 |           ppu.nmiCounter--;
124 |           if (ppu.nmiCounter === 0) {
125 |             ppu.requestEndFrame = false;
126 |             ppu.startVBlank();
127 |             break FRAMELOOP;
128 |           }
129 |         }
130 | 
131 |         ppu.curX++;
132 |         if (ppu.curX === 341) {
133 |           ppu.curX = 0;
134 |           ppu.endScanline();
135 |         }
136 |       }
137 |     }
138 |     this.fpsFrameCount++;
139 |   },
140 | 
141 |   buttonDown: function (controller, button) {
142 |     this.controllers[controller].buttonDown(button);
143 |   },
144 | 
145 |   buttonUp: function (controller, button) {
146 |     this.controllers[controller].buttonUp(button);
147 |   },
148 | 
149 |   zapperMove: function (x, y) {
150 |     if (!this.mmap) return;
151 |     this.mmap.zapperX = x;
152 |     this.mmap.zapperY = y;
153 |   },
154 | 
155 |   zapperFireDown: function () {
156 |     if (!this.mmap) return;
157 |     this.mmap.zapperFired = true;
158 |   },
159 | 
160 |   zapperFireUp: function () {
161 |     if (!this.mmap) return;
162 |     this.mmap.zapperFired = false;
163 |   },
164 | 
165 |   getFPS: function () {
166 |     var now = +new Date();
167 |     var fps = null;
168 |     if (this.lastFpsTime) {
169 |       fps = this.fpsFrameCount / ((now - this.lastFpsTime) / 1000);
170 |     }
171 |     this.fpsFrameCount = 0;
172 |     this.lastFpsTime = now;
173 |     return fps;
174 |   },
175 | 
176 |   reloadROM: function () {
177 |     if (this.romData !== null) {
178 |       this.loadROM(this.romData);
179 |     }
180 |   },
181 | 
182 |   // Loads a ROM file into the CPU and PPU.
183 |   // The ROM file is validated first.
184 |   loadROM: function (data) {
185 |     // Load ROM file:
186 |     this.rom = new ROM(this);
187 |     this.rom.load(data);
188 | 
189 |     this.reset();
190 |     this.mmap = this.rom.createMapper();
191 |     this.mmap.loadROM();
192 |     this.ppu.setMirroring(this.rom.getMirroringType());
193 |     this.romData = data;
194 |   },
195 | 
196 |   setFramerate: function (rate) {
197 |     this.opts.preferredFrameRate = rate;
198 |     this.frameTime = 1000 / rate;
199 |     this.papu.setSampleRate(this.opts.sampleRate, false);
200 |   },
201 | 
202 |   toJSON: function () {
203 |     return {
204 |       // romData: this.romData,
205 |       cpu: this.cpu.toJSON(),
206 |       mmap: this.mmap.toJSON(),
207 |       ppu: this.ppu.toJSON(),
208 |       papu: this.papu.toJSON(),
209 |     };
210 |   },
211 | 
212 |   fromJSON: function (s) {
213 |     this.reset();
214 |     // this.romData = s.romData;
215 |     this.cpu.fromJSON(s.cpu);
216 |     this.mmap.fromJSON(s.mmap);
217 |     this.ppu.fromJSON(s.ppu);
218 |     this.papu.fromJSON(s.papu);
219 |   },
220 | };
221 | 
222 | module.exports = NES;
223 | 


--------------------------------------------------------------------------------
/src/papu.js:
--------------------------------------------------------------------------------
   1 | var utils = require("./utils");
   2 | 
   3 | var CPU_FREQ_NTSC = 1789772.5; //1789772.72727272d;
   4 | // var CPU_FREQ_PAL = 1773447.4;
   5 | 
   6 | var PAPU = function (nes) {
   7 |   this.nes = nes;
   8 | 
   9 |   this.square1 = new ChannelSquare(this, true);
  10 |   this.square2 = new ChannelSquare(this, false);
  11 |   this.triangle = new ChannelTriangle(this);
  12 |   this.noise = new ChannelNoise(this);
  13 |   this.dmc = new ChannelDM(this);
  14 | 
  15 |   this.frameIrqCounter = null;
  16 |   this.frameIrqCounterMax = 4;
  17 |   this.initCounter = 2048;
  18 |   this.channelEnableValue = null;
  19 | 
  20 |   this.sampleRate = 44100;
  21 | 
  22 |   this.lengthLookup = null;
  23 |   this.dmcFreqLookup = null;
  24 |   this.noiseWavelengthLookup = null;
  25 |   this.square_table = null;
  26 |   this.tnd_table = null;
  27 | 
  28 |   this.frameIrqEnabled = false;
  29 |   this.frameIrqActive = null;
  30 |   this.frameClockNow = null;
  31 |   this.startedPlaying = false;
  32 |   this.recordOutput = false;
  33 |   this.initingHardware = false;
  34 | 
  35 |   this.masterFrameCounter = null;
  36 |   this.derivedFrameCounter = null;
  37 |   this.countSequence = null;
  38 |   this.sampleTimer = null;
  39 |   this.frameTime = null;
  40 |   this.sampleTimerMax = null;
  41 |   this.sampleCount = null;
  42 |   this.triValue = 0;
  43 | 
  44 |   this.smpSquare1 = null;
  45 |   this.smpSquare2 = null;
  46 |   this.smpTriangle = null;
  47 |   this.smpDmc = null;
  48 |   this.accCount = null;
  49 | 
  50 |   // DC removal vars:
  51 |   this.prevSampleL = 0;
  52 |   this.prevSampleR = 0;
  53 |   this.smpAccumL = 0;
  54 |   this.smpAccumR = 0;
  55 | 
  56 |   // DAC range:
  57 |   this.dacRange = 0;
  58 |   this.dcValue = 0;
  59 | 
  60 |   // Master volume:
  61 |   this.masterVolume = 256;
  62 | 
  63 |   // Stereo positioning:
  64 |   this.stereoPosLSquare1 = null;
  65 |   this.stereoPosLSquare2 = null;
  66 |   this.stereoPosLTriangle = null;
  67 |   this.stereoPosLNoise = null;
  68 |   this.stereoPosLDMC = null;
  69 |   this.stereoPosRSquare1 = null;
  70 |   this.stereoPosRSquare2 = null;
  71 |   this.stereoPosRTriangle = null;
  72 |   this.stereoPosRNoise = null;
  73 |   this.stereoPosRDMC = null;
  74 | 
  75 |   this.extraCycles = null;
  76 | 
  77 |   this.maxSample = null;
  78 |   this.minSample = null;
  79 | 
  80 |   // Panning:
  81 |   this.panning = [80, 170, 100, 150, 128];
  82 |   this.setPanning(this.panning);
  83 | 
  84 |   // Initialize lookup tables:
  85 |   this.initLengthLookup();
  86 |   this.initDmcFrequencyLookup();
  87 |   this.initNoiseWavelengthLookup();
  88 |   this.initDACtables();
  89 | 
  90 |   // Init sound registers:
  91 |   for (var i = 0; i < 0x14; i++) {
  92 |     if (i === 0x10) {
  93 |       this.writeReg(0x4010, 0x10);
  94 |     } else {
  95 |       this.writeReg(0x4000 + i, 0);
  96 |     }
  97 |   }
  98 | 
  99 |   this.reset();
 100 | };
 101 | 
 102 | PAPU.prototype = {
 103 |   reset: function () {
 104 |     this.sampleRate = this.nes.opts.sampleRate;
 105 |     this.sampleTimerMax = Math.floor(
 106 |       (1024.0 * CPU_FREQ_NTSC * this.nes.opts.preferredFrameRate) /
 107 |         (this.sampleRate * 60.0)
 108 |     );
 109 | 
 110 |     this.frameTime = Math.floor(
 111 |       (14915.0 * this.nes.opts.preferredFrameRate) / 60.0
 112 |     );
 113 | 
 114 |     this.sampleTimer = 0;
 115 | 
 116 |     this.updateChannelEnable(0);
 117 |     this.masterFrameCounter = 0;
 118 |     this.derivedFrameCounter = 0;
 119 |     this.countSequence = 0;
 120 |     this.sampleCount = 0;
 121 |     this.initCounter = 2048;
 122 |     this.frameIrqEnabled = false;
 123 |     this.initingHardware = false;
 124 | 
 125 |     this.resetCounter();
 126 | 
 127 |     this.square1.reset();
 128 |     this.square2.reset();
 129 |     this.triangle.reset();
 130 |     this.noise.reset();
 131 |     this.dmc.reset();
 132 | 
 133 |     this.accCount = 0;
 134 |     this.smpSquare1 = 0;
 135 |     this.smpSquare2 = 0;
 136 |     this.smpTriangle = 0;
 137 |     this.smpDmc = 0;
 138 | 
 139 |     this.frameIrqEnabled = false;
 140 |     this.frameIrqCounterMax = 4;
 141 | 
 142 |     this.channelEnableValue = 0xff;
 143 |     this.startedPlaying = false;
 144 |     this.prevSampleL = 0;
 145 |     this.prevSampleR = 0;
 146 |     this.smpAccumL = 0;
 147 |     this.smpAccumR = 0;
 148 | 
 149 |     this.maxSample = -500000;
 150 |     this.minSample = 500000;
 151 |   },
 152 | 
 153 |   // eslint-disable-next-line no-unused-vars
 154 |   readReg: function (address) {
 155 |     // Read 0x4015:
 156 |     var tmp = 0;
 157 |     tmp |= this.square1.getLengthStatus();
 158 |     tmp |= this.square2.getLengthStatus() << 1;
 159 |     tmp |= this.triangle.getLengthStatus() << 2;
 160 |     tmp |= this.noise.getLengthStatus() << 3;
 161 |     tmp |= this.dmc.getLengthStatus() << 4;
 162 |     tmp |= (this.frameIrqActive && this.frameIrqEnabled ? 1 : 0) << 6;
 163 |     tmp |= this.dmc.getIrqStatus() << 7;
 164 | 
 165 |     this.frameIrqActive = false;
 166 |     this.dmc.irqGenerated = false;
 167 | 
 168 |     return tmp & 0xffff;
 169 |   },
 170 | 
 171 |   writeReg: function (address, value) {
 172 |     if (address >= 0x4000 && address < 0x4004) {
 173 |       // Square Wave 1 Control
 174 |       this.square1.writeReg(address, value);
 175 |       // console.log("Square Write");
 176 |     } else if (address >= 0x4004 && address < 0x4008) {
 177 |       // Square 2 Control
 178 |       this.square2.writeReg(address, value);
 179 |     } else if (address >= 0x4008 && address < 0x400c) {
 180 |       // Triangle Control
 181 |       this.triangle.writeReg(address, value);
 182 |     } else if (address >= 0x400c && address <= 0x400f) {
 183 |       // Noise Control
 184 |       this.noise.writeReg(address, value);
 185 |     } else if (address === 0x4010) {
 186 |       // DMC Play mode & DMA frequency
 187 |       this.dmc.writeReg(address, value);
 188 |     } else if (address === 0x4011) {
 189 |       // DMC Delta Counter
 190 |       this.dmc.writeReg(address, value);
 191 |     } else if (address === 0x4012) {
 192 |       // DMC Play code starting address
 193 |       this.dmc.writeReg(address, value);
 194 |     } else if (address === 0x4013) {
 195 |       // DMC Play code length
 196 |       this.dmc.writeReg(address, value);
 197 |     } else if (address === 0x4015) {
 198 |       // Channel enable
 199 |       this.updateChannelEnable(value);
 200 | 
 201 |       if (value !== 0 && this.initCounter > 0) {
 202 |         // Start hardware initialization
 203 |         this.initingHardware = true;
 204 |       }
 205 | 
 206 |       // DMC/IRQ Status
 207 |       this.dmc.writeReg(address, value);
 208 |     } else if (address === 0x4017) {
 209 |       // Frame counter control
 210 |       this.countSequence = (value >> 7) & 1;
 211 |       this.masterFrameCounter = 0;
 212 |       this.frameIrqActive = false;
 213 | 
 214 |       if (((value >> 6) & 0x1) === 0) {
 215 |         this.frameIrqEnabled = true;
 216 |       } else {
 217 |         this.frameIrqEnabled = false;
 218 |       }
 219 | 
 220 |       if (this.countSequence === 0) {
 221 |         // NTSC:
 222 |         this.frameIrqCounterMax = 4;
 223 |         this.derivedFrameCounter = 4;
 224 |       } else {
 225 |         // PAL:
 226 |         this.frameIrqCounterMax = 5;
 227 |         this.derivedFrameCounter = 0;
 228 |         this.frameCounterTick();
 229 |       }
 230 |     }
 231 |   },
 232 | 
 233 |   resetCounter: function () {
 234 |     if (this.countSequence === 0) {
 235 |       this.derivedFrameCounter = 4;
 236 |     } else {
 237 |       this.derivedFrameCounter = 0;
 238 |     }
 239 |   },
 240 | 
 241 |   // Updates channel enable status.
 242 |   // This is done on writes to the
 243 |   // channel enable register (0x4015),
 244 |   // and when the user enables/disables channels
 245 |   // in the GUI.
 246 |   updateChannelEnable: function (value) {
 247 |     this.channelEnableValue = value & 0xffff;
 248 |     this.square1.setEnabled((value & 1) !== 0);
 249 |     this.square2.setEnabled((value & 2) !== 0);
 250 |     this.triangle.setEnabled((value & 4) !== 0);
 251 |     this.noise.setEnabled((value & 8) !== 0);
 252 |     this.dmc.setEnabled((value & 16) !== 0);
 253 |   },
 254 | 
 255 |   // Clocks the frame counter. It should be clocked at
 256 |   // twice the cpu speed, so the cycles will be
 257 |   // divided by 2 for those counters that are
 258 |   // clocked at cpu speed.
 259 |   clockFrameCounter: function (nCycles) {
 260 |     if (this.initCounter > 0) {
 261 |       if (this.initingHardware) {
 262 |         this.initCounter -= nCycles;
 263 |         if (this.initCounter <= 0) {
 264 |           this.initingHardware = false;
 265 |         }
 266 |         return;
 267 |       }
 268 |     }
 269 | 
 270 |     // Don't process ticks beyond next sampling:
 271 |     nCycles += this.extraCycles;
 272 |     var maxCycles = this.sampleTimerMax - this.sampleTimer;
 273 |     if (nCycles << 10 > maxCycles) {
 274 |       this.extraCycles = ((nCycles << 10) - maxCycles) >> 10;
 275 |       nCycles -= this.extraCycles;
 276 |     } else {
 277 |       this.extraCycles = 0;
 278 |     }
 279 | 
 280 |     var dmc = this.dmc;
 281 |     var triangle = this.triangle;
 282 |     var square1 = this.square1;
 283 |     var square2 = this.square2;
 284 |     var noise = this.noise;
 285 | 
 286 |     // Clock DMC:
 287 |     if (dmc.isEnabled) {
 288 |       dmc.shiftCounter -= nCycles << 3;
 289 |       while (dmc.shiftCounter <= 0 && dmc.dmaFrequency > 0) {
 290 |         dmc.shiftCounter += dmc.dmaFrequency;
 291 |         dmc.clockDmc();
 292 |       }
 293 |     }
 294 | 
 295 |     // Clock Triangle channel Prog timer:
 296 |     if (triangle.progTimerMax > 0) {
 297 |       triangle.progTimerCount -= nCycles;
 298 |       while (triangle.progTimerCount <= 0) {
 299 |         triangle.progTimerCount += triangle.progTimerMax + 1;
 300 |         if (triangle.linearCounter > 0 && triangle.lengthCounter > 0) {
 301 |           triangle.triangleCounter++;
 302 |           triangle.triangleCounter &= 0x1f;
 303 | 
 304 |           if (triangle.isEnabled) {
 305 |             if (triangle.triangleCounter >= 0x10) {
 306 |               // Normal value.
 307 |               triangle.sampleValue = triangle.triangleCounter & 0xf;
 308 |             } else {
 309 |               // Inverted value.
 310 |               triangle.sampleValue = 0xf - (triangle.triangleCounter & 0xf);
 311 |             }
 312 |             triangle.sampleValue <<= 4;
 313 |           }
 314 |         }
 315 |       }
 316 |     }
 317 | 
 318 |     // Clock Square channel 1 Prog timer:
 319 |     square1.progTimerCount -= nCycles;
 320 |     if (square1.progTimerCount <= 0) {
 321 |       square1.progTimerCount += (square1.progTimerMax + 1) << 1;
 322 | 
 323 |       square1.squareCounter++;
 324 |       square1.squareCounter &= 0x7;
 325 |       square1.updateSampleValue();
 326 |     }
 327 | 
 328 |     // Clock Square channel 2 Prog timer:
 329 |     square2.progTimerCount -= nCycles;
 330 |     if (square2.progTimerCount <= 0) {
 331 |       square2.progTimerCount += (square2.progTimerMax + 1) << 1;
 332 | 
 333 |       square2.squareCounter++;
 334 |       square2.squareCounter &= 0x7;
 335 |       square2.updateSampleValue();
 336 |     }
 337 | 
 338 |     // Clock noise channel Prog timer:
 339 |     var acc_c = nCycles;
 340 |     if (noise.progTimerCount - acc_c > 0) {
 341 |       // Do all cycles at once:
 342 |       noise.progTimerCount -= acc_c;
 343 |       noise.accCount += acc_c;
 344 |       noise.accValue += acc_c * noise.sampleValue;
 345 |     } else {
 346 |       // Slow-step:
 347 |       while (acc_c-- > 0) {
 348 |         if (--noise.progTimerCount <= 0 && noise.progTimerMax > 0) {
 349 |           // Update noise shift register:
 350 |           noise.shiftReg <<= 1;
 351 |           noise.tmp =
 352 |             ((noise.shiftReg << (noise.randomMode === 0 ? 1 : 6)) ^
 353 |               noise.shiftReg) &
 354 |             0x8000;
 355 |           if (noise.tmp !== 0) {
 356 |             // Sample value must be 0.
 357 |             noise.shiftReg |= 0x01;
 358 |             noise.randomBit = 0;
 359 |             noise.sampleValue = 0;
 360 |           } else {
 361 |             // Find sample value:
 362 |             noise.randomBit = 1;
 363 |             if (noise.isEnabled && noise.lengthCounter > 0) {
 364 |               noise.sampleValue = noise.masterVolume;
 365 |             } else {
 366 |               noise.sampleValue = 0;
 367 |             }
 368 |           }
 369 | 
 370 |           noise.progTimerCount += noise.progTimerMax;
 371 |         }
 372 | 
 373 |         noise.accValue += noise.sampleValue;
 374 |         noise.accCount++;
 375 |       }
 376 |     }
 377 | 
 378 |     // Frame IRQ handling:
 379 |     if (this.frameIrqEnabled && this.frameIrqActive) {
 380 |       this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NORMAL);
 381 |     }
 382 | 
 383 |     // Clock frame counter at double CPU speed:
 384 |     this.masterFrameCounter += nCycles << 1;
 385 |     if (this.masterFrameCounter >= this.frameTime) {
 386 |       // 240Hz tick:
 387 |       this.masterFrameCounter -= this.frameTime;
 388 |       this.frameCounterTick();
 389 |     }
 390 | 
 391 |     // Accumulate sample value:
 392 |     this.accSample(nCycles);
 393 | 
 394 |     // Clock sample timer:
 395 |     this.sampleTimer += nCycles << 10;
 396 |     if (this.sampleTimer >= this.sampleTimerMax) {
 397 |       // Sample channels:
 398 |       this.sample();
 399 |       this.sampleTimer -= this.sampleTimerMax;
 400 |     }
 401 |   },
 402 | 
 403 |   accSample: function (cycles) {
 404 |     // Special treatment for triangle channel - need to interpolate.
 405 |     if (this.triangle.sampleCondition) {
 406 |       this.triValue = Math.floor(
 407 |         (this.triangle.progTimerCount << 4) / (this.triangle.progTimerMax + 1)
 408 |       );
 409 |       if (this.triValue > 16) {
 410 |         this.triValue = 16;
 411 |       }
 412 |       if (this.triangle.triangleCounter >= 16) {
 413 |         this.triValue = 16 - this.triValue;
 414 |       }
 415 | 
 416 |       // Add non-interpolated sample value:
 417 |       this.triValue += this.triangle.sampleValue;
 418 |     }
 419 | 
 420 |     // Now sample normally:
 421 |     if (cycles === 2) {
 422 |       this.smpTriangle += this.triValue << 1;
 423 |       this.smpDmc += this.dmc.sample << 1;
 424 |       this.smpSquare1 += this.square1.sampleValue << 1;
 425 |       this.smpSquare2 += this.square2.sampleValue << 1;
 426 |       this.accCount += 2;
 427 |     } else if (cycles === 4) {
 428 |       this.smpTriangle += this.triValue << 2;
 429 |       this.smpDmc += this.dmc.sample << 2;
 430 |       this.smpSquare1 += this.square1.sampleValue << 2;
 431 |       this.smpSquare2 += this.square2.sampleValue << 2;
 432 |       this.accCount += 4;
 433 |     } else {
 434 |       this.smpTriangle += cycles * this.triValue;
 435 |       this.smpDmc += cycles * this.dmc.sample;
 436 |       this.smpSquare1 += cycles * this.square1.sampleValue;
 437 |       this.smpSquare2 += cycles * this.square2.sampleValue;
 438 |       this.accCount += cycles;
 439 |     }
 440 |   },
 441 | 
 442 |   frameCounterTick: function () {
 443 |     this.derivedFrameCounter++;
 444 |     if (this.derivedFrameCounter >= this.frameIrqCounterMax) {
 445 |       this.derivedFrameCounter = 0;
 446 |     }
 447 | 
 448 |     if (this.derivedFrameCounter === 1 || this.derivedFrameCounter === 3) {
 449 |       // Clock length & sweep:
 450 |       this.triangle.clockLengthCounter();
 451 |       this.square1.clockLengthCounter();
 452 |       this.square2.clockLengthCounter();
 453 |       this.noise.clockLengthCounter();
 454 |       this.square1.clockSweep();
 455 |       this.square2.clockSweep();
 456 |     }
 457 | 
 458 |     if (this.derivedFrameCounter >= 0 && this.derivedFrameCounter < 4) {
 459 |       // Clock linear & decay:
 460 |       this.square1.clockEnvDecay();
 461 |       this.square2.clockEnvDecay();
 462 |       this.noise.clockEnvDecay();
 463 |       this.triangle.clockLinearCounter();
 464 |     }
 465 | 
 466 |     if (this.derivedFrameCounter === 3 && this.countSequence === 0) {
 467 |       // Enable IRQ:
 468 |       this.frameIrqActive = true;
 469 |     }
 470 | 
 471 |     // End of 240Hz tick
 472 |   },
 473 | 
 474 |   // Samples the channels, mixes the output together, then writes to buffer.
 475 |   sample: function () {
 476 |     var sq_index, tnd_index;
 477 | 
 478 |     if (this.accCount > 0) {
 479 |       this.smpSquare1 <<= 4;
 480 |       this.smpSquare1 = Math.floor(this.smpSquare1 / this.accCount);
 481 | 
 482 |       this.smpSquare2 <<= 4;
 483 |       this.smpSquare2 = Math.floor(this.smpSquare2 / this.accCount);
 484 | 
 485 |       this.smpTriangle = Math.floor(this.smpTriangle / this.accCount);
 486 | 
 487 |       this.smpDmc <<= 4;
 488 |       this.smpDmc = Math.floor(this.smpDmc / this.accCount);
 489 | 
 490 |       this.accCount = 0;
 491 |     } else {
 492 |       this.smpSquare1 = this.square1.sampleValue << 4;
 493 |       this.smpSquare2 = this.square2.sampleValue << 4;
 494 |       this.smpTriangle = this.triangle.sampleValue;
 495 |       this.smpDmc = this.dmc.sample << 4;
 496 |     }
 497 | 
 498 |     var smpNoise = Math.floor((this.noise.accValue << 4) / this.noise.accCount);
 499 |     this.noise.accValue = smpNoise >> 4;
 500 |     this.noise.accCount = 1;
 501 | 
 502 |     // Stereo sound.
 503 | 
 504 |     // Left channel:
 505 |     sq_index =
 506 |       (this.smpSquare1 * this.stereoPosLSquare1 +
 507 |         this.smpSquare2 * this.stereoPosLSquare2) >>
 508 |       8;
 509 |     tnd_index =
 510 |       (3 * this.smpTriangle * this.stereoPosLTriangle +
 511 |         (smpNoise << 1) * this.stereoPosLNoise +
 512 |         this.smpDmc * this.stereoPosLDMC) >>
 513 |       8;
 514 |     if (sq_index >= this.square_table.length) {
 515 |       sq_index = this.square_table.length - 1;
 516 |     }
 517 |     if (tnd_index >= this.tnd_table.length) {
 518 |       tnd_index = this.tnd_table.length - 1;
 519 |     }
 520 |     var sampleValueL =
 521 |       this.square_table[sq_index] + this.tnd_table[tnd_index] - this.dcValue;
 522 | 
 523 |     // Right channel:
 524 |     sq_index =
 525 |       (this.smpSquare1 * this.stereoPosRSquare1 +
 526 |         this.smpSquare2 * this.stereoPosRSquare2) >>
 527 |       8;
 528 |     tnd_index =
 529 |       (3 * this.smpTriangle * this.stereoPosRTriangle +
 530 |         (smpNoise << 1) * this.stereoPosRNoise +
 531 |         this.smpDmc * this.stereoPosRDMC) >>
 532 |       8;
 533 |     if (sq_index >= this.square_table.length) {
 534 |       sq_index = this.square_table.length - 1;
 535 |     }
 536 |     if (tnd_index >= this.tnd_table.length) {
 537 |       tnd_index = this.tnd_table.length - 1;
 538 |     }
 539 |     var sampleValueR =
 540 |       this.square_table[sq_index] + this.tnd_table[tnd_index] - this.dcValue;
 541 | 
 542 |     // Remove DC from left channel:
 543 |     var smpDiffL = sampleValueL - this.prevSampleL;
 544 |     this.prevSampleL += smpDiffL;
 545 |     this.smpAccumL += smpDiffL - (this.smpAccumL >> 10);
 546 |     sampleValueL = this.smpAccumL;
 547 | 
 548 |     // Remove DC from right channel:
 549 |     var smpDiffR = sampleValueR - this.prevSampleR;
 550 |     this.prevSampleR += smpDiffR;
 551 |     this.smpAccumR += smpDiffR - (this.smpAccumR >> 10);
 552 |     sampleValueR = this.smpAccumR;
 553 | 
 554 |     // Write:
 555 |     if (sampleValueL > this.maxSample) {
 556 |       this.maxSample = sampleValueL;
 557 |     }
 558 |     if (sampleValueL < this.minSample) {
 559 |       this.minSample = sampleValueL;
 560 |     }
 561 | 
 562 |     if (this.nes.opts.onAudioSample) {
 563 |       this.nes.opts.onAudioSample(sampleValueL / 32768, sampleValueR / 32768);
 564 |     }
 565 | 
 566 |     // Reset sampled values:
 567 |     this.smpSquare1 = 0;
 568 |     this.smpSquare2 = 0;
 569 |     this.smpTriangle = 0;
 570 |     this.smpDmc = 0;
 571 |   },
 572 | 
 573 |   getLengthMax: function (value) {
 574 |     return this.lengthLookup[value >> 3];
 575 |   },
 576 | 
 577 |   getDmcFrequency: function (value) {
 578 |     if (value >= 0 && value < 0x10) {
 579 |       return this.dmcFreqLookup[value];
 580 |     }
 581 |     return 0;
 582 |   },
 583 | 
 584 |   getNoiseWaveLength: function (value) {
 585 |     if (value >= 0 && value < 0x10) {
 586 |       return this.noiseWavelengthLookup[value];
 587 |     }
 588 |     return 0;
 589 |   },
 590 | 
 591 |   setPanning: function (pos) {
 592 |     for (var i = 0; i < 5; i++) {
 593 |       this.panning[i] = pos[i];
 594 |     }
 595 |     this.updateStereoPos();
 596 |   },
 597 | 
 598 |   setMasterVolume: function (value) {
 599 |     if (value < 0) {
 600 |       value = 0;
 601 |     }
 602 |     if (value > 256) {
 603 |       value = 256;
 604 |     }
 605 |     this.masterVolume = value;
 606 |     this.updateStereoPos();
 607 |   },
 608 | 
 609 |   updateStereoPos: function () {
 610 |     this.stereoPosLSquare1 = (this.panning[0] * this.masterVolume) >> 8;
 611 |     this.stereoPosLSquare2 = (this.panning[1] * this.masterVolume) >> 8;
 612 |     this.stereoPosLTriangle = (this.panning[2] * this.masterVolume) >> 8;
 613 |     this.stereoPosLNoise = (this.panning[3] * this.masterVolume) >> 8;
 614 |     this.stereoPosLDMC = (this.panning[4] * this.masterVolume) >> 8;
 615 | 
 616 |     this.stereoPosRSquare1 = this.masterVolume - this.stereoPosLSquare1;
 617 |     this.stereoPosRSquare2 = this.masterVolume - this.stereoPosLSquare2;
 618 |     this.stereoPosRTriangle = this.masterVolume - this.stereoPosLTriangle;
 619 |     this.stereoPosRNoise = this.masterVolume - this.stereoPosLNoise;
 620 |     this.stereoPosRDMC = this.masterVolume - this.stereoPosLDMC;
 621 |   },
 622 | 
 623 |   initLengthLookup: function () {
 624 |     // prettier-ignore
 625 |     this.lengthLookup = [
 626 |             0x0A, 0xFE,
 627 |             0x14, 0x02,
 628 |             0x28, 0x04,
 629 |             0x50, 0x06,
 630 |             0xA0, 0x08,
 631 |             0x3C, 0x0A,
 632 |             0x0E, 0x0C,
 633 |             0x1A, 0x0E,
 634 |             0x0C, 0x10,
 635 |             0x18, 0x12,
 636 |             0x30, 0x14,
 637 |             0x60, 0x16,
 638 |             0xC0, 0x18,
 639 |             0x48, 0x1A,
 640 |             0x10, 0x1C,
 641 |             0x20, 0x1E
 642 |         ];
 643 |   },
 644 | 
 645 |   initDmcFrequencyLookup: function () {
 646 |     this.dmcFreqLookup = new Array(16);
 647 | 
 648 |     this.dmcFreqLookup[0x0] = 0xd60;
 649 |     this.dmcFreqLookup[0x1] = 0xbe0;
 650 |     this.dmcFreqLookup[0x2] = 0xaa0;
 651 |     this.dmcFreqLookup[0x3] = 0xa00;
 652 |     this.dmcFreqLookup[0x4] = 0x8f0;
 653 |     this.dmcFreqLookup[0x5] = 0x7f0;
 654 |     this.dmcFreqLookup[0x6] = 0x710;
 655 |     this.dmcFreqLookup[0x7] = 0x6b0;
 656 |     this.dmcFreqLookup[0x8] = 0x5f0;
 657 |     this.dmcFreqLookup[0x9] = 0x500;
 658 |     this.dmcFreqLookup[0xa] = 0x470;
 659 |     this.dmcFreqLookup[0xb] = 0x400;
 660 |     this.dmcFreqLookup[0xc] = 0x350;
 661 |     this.dmcFreqLookup[0xd] = 0x2a0;
 662 |     this.dmcFreqLookup[0xe] = 0x240;
 663 |     this.dmcFreqLookup[0xf] = 0x1b0;
 664 |     //for(int i=0;i<16;i++)dmcFreqLookup[i]/=8;
 665 |   },
 666 | 
 667 |   initNoiseWavelengthLookup: function () {
 668 |     this.noiseWavelengthLookup = new Array(16);
 669 | 
 670 |     this.noiseWavelengthLookup[0x0] = 0x004;
 671 |     this.noiseWavelengthLookup[0x1] = 0x008;
 672 |     this.noiseWavelengthLookup[0x2] = 0x010;
 673 |     this.noiseWavelengthLookup[0x3] = 0x020;
 674 |     this.noiseWavelengthLookup[0x4] = 0x040;
 675 |     this.noiseWavelengthLookup[0x5] = 0x060;
 676 |     this.noiseWavelengthLookup[0x6] = 0x080;
 677 |     this.noiseWavelengthLookup[0x7] = 0x0a0;
 678 |     this.noiseWavelengthLookup[0x8] = 0x0ca;
 679 |     this.noiseWavelengthLookup[0x9] = 0x0fe;
 680 |     this.noiseWavelengthLookup[0xa] = 0x17c;
 681 |     this.noiseWavelengthLookup[0xb] = 0x1fc;
 682 |     this.noiseWavelengthLookup[0xc] = 0x2fa;
 683 |     this.noiseWavelengthLookup[0xd] = 0x3f8;
 684 |     this.noiseWavelengthLookup[0xe] = 0x7f2;
 685 |     this.noiseWavelengthLookup[0xf] = 0xfe4;
 686 |   },
 687 | 
 688 |   initDACtables: function () {
 689 |     var value, ival, i;
 690 |     var max_sqr = 0;
 691 |     var max_tnd = 0;
 692 | 
 693 |     this.square_table = new Array(32 * 16);
 694 |     this.tnd_table = new Array(204 * 16);
 695 | 
 696 |     for (i = 0; i < 32 * 16; i++) {
 697 |       value = 95.52 / (8128.0 / (i / 16.0) + 100.0);
 698 |       value *= 0.98411;
 699 |       value *= 50000.0;
 700 |       ival = Math.floor(value);
 701 | 
 702 |       this.square_table[i] = ival;
 703 |       if (ival > max_sqr) {
 704 |         max_sqr = ival;
 705 |       }
 706 |     }
 707 | 
 708 |     for (i = 0; i < 204 * 16; i++) {
 709 |       value = 163.67 / (24329.0 / (i / 16.0) + 100.0);
 710 |       value *= 0.98411;
 711 |       value *= 50000.0;
 712 |       ival = Math.floor(value);
 713 | 
 714 |       this.tnd_table[i] = ival;
 715 |       if (ival > max_tnd) {
 716 |         max_tnd = ival;
 717 |       }
 718 |     }
 719 | 
 720 |     this.dacRange = max_sqr + max_tnd;
 721 |     this.dcValue = this.dacRange / 2;
 722 |   },
 723 | 
 724 |   JSON_PROPERTIES: [
 725 |     "frameIrqCounter",
 726 |     "frameIrqCounterMax",
 727 |     "initCounter",
 728 |     "channelEnableValue",
 729 |     "sampleRate",
 730 |     "frameIrqEnabled",
 731 |     "frameIrqActive",
 732 |     "frameClockNow",
 733 |     "startedPlaying",
 734 |     "recordOutput",
 735 |     "initingHardware",
 736 |     "masterFrameCounter",
 737 |     "derivedFrameCounter",
 738 |     "countSequence",
 739 |     "sampleTimer",
 740 |     "frameTime",
 741 |     "sampleTimerMax",
 742 |     "sampleCount",
 743 |     "triValue",
 744 |     "smpSquare1",
 745 |     "smpSquare2",
 746 |     "smpTriangle",
 747 |     "smpDmc",
 748 |     "accCount",
 749 |     "prevSampleL",
 750 |     "prevSampleR",
 751 |     "smpAccumL",
 752 |     "smpAccumR",
 753 |     "masterVolume",
 754 |     "stereoPosLSquare1",
 755 |     "stereoPosLSquare2",
 756 |     "stereoPosLTriangle",
 757 |     "stereoPosLNoise",
 758 |     "stereoPosLDMC",
 759 |     "stereoPosRSquare1",
 760 |     "stereoPosRSquare2",
 761 |     "stereoPosRTriangle",
 762 |     "stereoPosRNoise",
 763 |     "stereoPosRDMC",
 764 |     "extraCycles",
 765 |     "maxSample",
 766 |     "minSample",
 767 |     "panning",
 768 |   ],
 769 | 
 770 |   toJSON: function () {
 771 |     let obj = utils.toJSON(this);
 772 |     obj.dmc = this.dmc.toJSON();
 773 |     obj.noise = this.noise.toJSON();
 774 |     obj.square1 = this.square1.toJSON();
 775 |     obj.square2 = this.square2.toJSON();
 776 |     obj.triangle = this.triangle.toJSON();
 777 |     return obj;
 778 |   },
 779 | 
 780 |   fromJSON: function (s) {
 781 |     utils.fromJSON(this, s);
 782 |     this.dmc.fromJSON(s.dmc);
 783 |     this.noise.fromJSON(s.noise);
 784 |     this.square1.fromJSON(s.square1);
 785 |     this.square2.fromJSON(s.square2);
 786 |     this.triangle.fromJSON(s.triangle);
 787 |   },
 788 | };
 789 | 
 790 | var ChannelDM = function (papu) {
 791 |   this.papu = papu;
 792 | 
 793 |   this.MODE_NORMAL = 0;
 794 |   this.MODE_LOOP = 1;
 795 |   this.MODE_IRQ = 2;
 796 | 
 797 |   this.isEnabled = null;
 798 |   this.hasSample = null;
 799 |   this.irqGenerated = false;
 800 | 
 801 |   this.playMode = null;
 802 |   this.dmaFrequency = null;
 803 |   this.dmaCounter = null;
 804 |   this.deltaCounter = null;
 805 |   this.playStartAddress = null;
 806 |   this.playAddress = null;
 807 |   this.playLength = null;
 808 |   this.playLengthCounter = null;
 809 |   this.shiftCounter = null;
 810 |   this.reg4012 = null;
 811 |   this.reg4013 = null;
 812 |   this.sample = null;
 813 |   this.dacLsb = null;
 814 |   this.data = null;
 815 | 
 816 |   this.reset();
 817 | };
 818 | 
 819 | ChannelDM.prototype = {
 820 |   clockDmc: function () {
 821 |     // Only alter DAC value if the sample buffer has data:
 822 |     if (this.hasSample) {
 823 |       if ((this.data & 1) === 0) {
 824 |         // Decrement delta:
 825 |         if (this.deltaCounter > 0) {
 826 |           this.deltaCounter--;
 827 |         }
 828 |       } else {
 829 |         // Increment delta:
 830 |         if (this.deltaCounter < 63) {
 831 |           this.deltaCounter++;
 832 |         }
 833 |       }
 834 | 
 835 |       // Update sample value:
 836 |       this.sample = this.isEnabled ? (this.deltaCounter << 1) + this.dacLsb : 0;
 837 | 
 838 |       // Update shift register:
 839 |       this.data >>= 1;
 840 |     }
 841 | 
 842 |     this.dmaCounter--;
 843 |     if (this.dmaCounter <= 0) {
 844 |       // No more sample bits.
 845 |       this.hasSample = false;
 846 |       this.endOfSample();
 847 |       this.dmaCounter = 8;
 848 |     }
 849 | 
 850 |     if (this.irqGenerated) {
 851 |       this.papu.nes.cpu.requestIrq(this.papu.nes.cpu.IRQ_NORMAL);
 852 |     }
 853 |   },
 854 | 
 855 |   endOfSample: function () {
 856 |     if (this.playLengthCounter === 0 && this.playMode === this.MODE_LOOP) {
 857 |       // Start from beginning of sample:
 858 |       this.playAddress = this.playStartAddress;
 859 |       this.playLengthCounter = this.playLength;
 860 |     }
 861 | 
 862 |     if (this.playLengthCounter > 0) {
 863 |       // Fetch next sample:
 864 |       this.nextSample();
 865 | 
 866 |       if (this.playLengthCounter === 0) {
 867 |         // Last byte of sample fetched, generate IRQ:
 868 |         if (this.playMode === this.MODE_IRQ) {
 869 |           // Generate IRQ:
 870 |           this.irqGenerated = true;
 871 |         }
 872 |       }
 873 |     }
 874 |   },
 875 | 
 876 |   nextSample: function () {
 877 |     // Fetch byte:
 878 |     this.data = this.papu.nes.mmap.load(this.playAddress);
 879 |     this.papu.nes.cpu.haltCycles(4);
 880 | 
 881 |     this.playLengthCounter--;
 882 |     this.playAddress++;
 883 |     if (this.playAddress > 0xffff) {
 884 |       this.playAddress = 0x8000;
 885 |     }
 886 | 
 887 |     this.hasSample = true;
 888 |   },
 889 | 
 890 |   writeReg: function (address, value) {
 891 |     if (address === 0x4010) {
 892 |       // Play mode, DMA Frequency
 893 |       if (value >> 6 === 0) {
 894 |         this.playMode = this.MODE_NORMAL;
 895 |       } else if (((value >> 6) & 1) === 1) {
 896 |         this.playMode = this.MODE_LOOP;
 897 |       } else if (value >> 6 === 2) {
 898 |         this.playMode = this.MODE_IRQ;
 899 |       }
 900 | 
 901 |       if ((value & 0x80) === 0) {
 902 |         this.irqGenerated = false;
 903 |       }
 904 | 
 905 |       this.dmaFrequency = this.papu.getDmcFrequency(value & 0xf);
 906 |     } else if (address === 0x4011) {
 907 |       // Delta counter load register:
 908 |       this.deltaCounter = (value >> 1) & 63;
 909 |       this.dacLsb = value & 1;
 910 |       this.sample = (this.deltaCounter << 1) + this.dacLsb; // update sample value
 911 |     } else if (address === 0x4012) {
 912 |       // DMA address load register
 913 |       this.playStartAddress = (value << 6) | 0x0c000;
 914 |       this.playAddress = this.playStartAddress;
 915 |       this.reg4012 = value;
 916 |     } else if (address === 0x4013) {
 917 |       // Length of play code
 918 |       this.playLength = (value << 4) + 1;
 919 |       this.playLengthCounter = this.playLength;
 920 |       this.reg4013 = value;
 921 |     } else if (address === 0x4015) {
 922 |       // DMC/IRQ Status
 923 |       if (((value >> 4) & 1) === 0) {
 924 |         // Disable:
 925 |         this.playLengthCounter = 0;
 926 |       } else {
 927 |         // Restart:
 928 |         this.playAddress = this.playStartAddress;
 929 |         this.playLengthCounter = this.playLength;
 930 |       }
 931 |       this.irqGenerated = false;
 932 |     }
 933 |   },
 934 | 
 935 |   setEnabled: function (value) {
 936 |     if (!this.isEnabled && value) {
 937 |       this.playLengthCounter = this.playLength;
 938 |     }
 939 |     this.isEnabled = value;
 940 |   },
 941 | 
 942 |   getLengthStatus: function () {
 943 |     return this.playLengthCounter === 0 || !this.isEnabled ? 0 : 1;
 944 |   },
 945 | 
 946 |   getIrqStatus: function () {
 947 |     return this.irqGenerated ? 1 : 0;
 948 |   },
 949 | 
 950 |   reset: function () {
 951 |     this.isEnabled = false;
 952 |     this.irqGenerated = false;
 953 |     this.playMode = this.MODE_NORMAL;
 954 |     this.dmaFrequency = 0;
 955 |     this.dmaCounter = 0;
 956 |     this.deltaCounter = 0;
 957 |     this.playStartAddress = 0;
 958 |     this.playAddress = 0;
 959 |     this.playLength = 0;
 960 |     this.playLengthCounter = 0;
 961 |     this.sample = 0;
 962 |     this.dacLsb = 0;
 963 |     this.shiftCounter = 0;
 964 |     this.reg4012 = 0;
 965 |     this.reg4013 = 0;
 966 |     this.data = 0;
 967 |   },
 968 | 
 969 |   JSON_PROPERTIES: [
 970 |     "MODE_NORMAL",
 971 |     "MODE_LOOP",
 972 |     "MODE_IRQ",
 973 |     "isEnabled",
 974 |     "hasSample",
 975 |     "irqGenerated",
 976 |     "playMode",
 977 |     "dmaFrequency",
 978 |     "dmaCounter",
 979 |     "deltaCounter",
 980 |     "playStartAddress",
 981 |     "playAddress",
 982 |     "playLength",
 983 |     "playLengthCounter",
 984 |     "shiftCounter",
 985 |     "reg4012",
 986 |     "reg4013",
 987 |     "sample",
 988 |     "dacLsb",
 989 |     "data",
 990 |   ],
 991 | 
 992 |   toJSON: function () {
 993 |     return utils.toJSON(this);
 994 |   },
 995 | 
 996 |   fromJSON: function (s) {
 997 |     utils.fromJSON(this, s);
 998 |   },
 999 | };
1000 | 
1001 | var ChannelNoise = function (papu) {
1002 |   this.papu = papu;
1003 | 
1004 |   this.isEnabled = null;
1005 |   this.envDecayDisable = null;
1006 |   this.envDecayLoopEnable = null;
1007 |   this.lengthCounterEnable = null;
1008 |   this.envReset = null;
1009 |   this.shiftNow = null;
1010 | 
1011 |   this.lengthCounter = null;
1012 |   this.progTimerCount = null;
1013 |   this.progTimerMax = null;
1014 |   this.envDecayRate = null;
1015 |   this.envDecayCounter = null;
1016 |   this.envVolume = null;
1017 |   this.masterVolume = null;
1018 |   this.shiftReg = 1 << 14;
1019 |   this.randomBit = null;
1020 |   this.randomMode = null;
1021 |   this.sampleValue = null;
1022 |   this.accValue = 0;
1023 |   this.accCount = 1;
1024 |   this.tmp = null;
1025 | 
1026 |   this.reset();
1027 | };
1028 | 
1029 | ChannelNoise.prototype = {
1030 |   reset: function () {
1031 |     this.progTimerCount = 0;
1032 |     this.progTimerMax = 0;
1033 |     this.isEnabled = false;
1034 |     this.lengthCounter = 0;
1035 |     this.lengthCounterEnable = false;
1036 |     this.envDecayDisable = false;
1037 |     this.envDecayLoopEnable = false;
1038 |     this.shiftNow = false;
1039 |     this.envDecayRate = 0;
1040 |     this.envDecayCounter = 0;
1041 |     this.envVolume = 0;
1042 |     this.masterVolume = 0;
1043 |     this.shiftReg = 1;
1044 |     this.randomBit = 0;
1045 |     this.randomMode = 0;
1046 |     this.sampleValue = 0;
1047 |     this.tmp = 0;
1048 |   },
1049 | 
1050 |   clockLengthCounter: function () {
1051 |     if (this.lengthCounterEnable && this.lengthCounter > 0) {
1052 |       this.lengthCounter--;
1053 |       if (this.lengthCounter === 0) {
1054 |         this.updateSampleValue();
1055 |       }
1056 |     }
1057 |   },
1058 | 
1059 |   clockEnvDecay: function () {
1060 |     if (this.envReset) {
1061 |       // Reset envelope:
1062 |       this.envReset = false;
1063 |       this.envDecayCounter = this.envDecayRate + 1;
1064 |       this.envVolume = 0xf;
1065 |     } else if (--this.envDecayCounter <= 0) {
1066 |       // Normal handling:
1067 |       this.envDecayCounter = this.envDecayRate + 1;
1068 |       if (this.envVolume > 0) {
1069 |         this.envVolume--;
1070 |       } else {
1071 |         this.envVolume = this.envDecayLoopEnable ? 0xf : 0;
1072 |       }
1073 |     }
1074 |     if (this.envDecayDisable) {
1075 |       this.masterVolume = this.envDecayRate;
1076 |     } else {
1077 |       this.masterVolume = this.envVolume;
1078 |     }
1079 |     this.updateSampleValue();
1080 |   },
1081 | 
1082 |   updateSampleValue: function () {
1083 |     if (this.isEnabled && this.lengthCounter > 0) {
1084 |       this.sampleValue = this.randomBit * this.masterVolume;
1085 |     }
1086 |   },
1087 | 
1088 |   writeReg: function (address, value) {
1089 |     if (address === 0x400c) {
1090 |       // Volume/Envelope decay:
1091 |       this.envDecayDisable = (value & 0x10) !== 0;
1092 |       this.envDecayRate = value & 0xf;
1093 |       this.envDecayLoopEnable = (value & 0x20) !== 0;
1094 |       this.lengthCounterEnable = (value & 0x20) === 0;
1095 |       if (this.envDecayDisable) {
1096 |         this.masterVolume = this.envDecayRate;
1097 |       } else {
1098 |         this.masterVolume = this.envVolume;
1099 |       }
1100 |     } else if (address === 0x400e) {
1101 |       // Programmable timer:
1102 |       this.progTimerMax = this.papu.getNoiseWaveLength(value & 0xf);
1103 |       this.randomMode = value >> 7;
1104 |     } else if (address === 0x400f) {
1105 |       // Length counter
1106 |       this.lengthCounter = this.papu.getLengthMax(value & 248);
1107 |       this.envReset = true;
1108 |     }
1109 |     // Update:
1110 |     //updateSampleValue();
1111 |   },
1112 | 
1113 |   setEnabled: function (value) {
1114 |     this.isEnabled = value;
1115 |     if (!value) {
1116 |       this.lengthCounter = 0;
1117 |     }
1118 |     this.updateSampleValue();
1119 |   },
1120 | 
1121 |   getLengthStatus: function () {
1122 |     return this.lengthCounter === 0 || !this.isEnabled ? 0 : 1;
1123 |   },
1124 | 
1125 |   JSON_PROPERTIES: [
1126 |     "isEnabled",
1127 |     "envDecayDisable",
1128 |     "envDecayLoopEnable",
1129 |     "lengthCounterEnable",
1130 |     "envReset",
1131 |     "shiftNow",
1132 |     "lengthCounter",
1133 |     "progTimerCount",
1134 |     "progTimerMax",
1135 |     "envDecayRate",
1136 |     "envDecayCounter",
1137 |     "envVolume",
1138 |     "masterVolume",
1139 |     "shiftReg",
1140 |     "randomBit",
1141 |     "randomMode",
1142 |     "sampleValue",
1143 |     "accValue",
1144 |     "accCount",
1145 |     "tmp",
1146 |   ],
1147 | 
1148 |   toJSON: function () {
1149 |     return utils.toJSON(this);
1150 |   },
1151 | 
1152 |   fromJSON: function (s) {
1153 |     utils.fromJSON(this, s);
1154 |   },
1155 | };
1156 | 
1157 | var ChannelSquare = function (papu, square1) {
1158 |   this.papu = papu;
1159 | 
1160 |   // prettier-ignore
1161 |   this.dutyLookup = [
1162 |          0, 1, 0, 0, 0, 0, 0, 0,
1163 |          0, 1, 1, 0, 0, 0, 0, 0,
1164 |          0, 1, 1, 1, 1, 0, 0, 0,
1165 |          1, 0, 0, 1, 1, 1, 1, 1
1166 |     ];
1167 |   // prettier-ignore
1168 |   this.impLookup = [
1169 |          1,-1, 0, 0, 0, 0, 0, 0,
1170 |          1, 0,-1, 0, 0, 0, 0, 0,
1171 |          1, 0, 0, 0,-1, 0, 0, 0,
1172 |         -1, 0, 1, 0, 0, 0, 0, 0
1173 |     ];
1174 | 
1175 |   this.sqr1 = square1;
1176 |   this.isEnabled = null;
1177 |   this.lengthCounterEnable = null;
1178 |   this.sweepActive = null;
1179 |   this.envDecayDisable = null;
1180 |   this.envDecayLoopEnable = null;
1181 |   this.envReset = null;
1182 |   this.sweepCarry = null;
1183 |   this.updateSweepPeriod = null;
1184 | 
1185 |   this.progTimerCount = null;
1186 |   this.progTimerMax = null;
1187 |   this.lengthCounter = null;
1188 |   this.squareCounter = null;
1189 |   this.sweepCounter = null;
1190 |   this.sweepCounterMax = null;
1191 |   this.sweepMode = null;
1192 |   this.sweepShiftAmount = null;
1193 |   this.envDecayRate = null;
1194 |   this.envDecayCounter = null;
1195 |   this.envVolume = null;
1196 |   this.masterVolume = null;
1197 |   this.dutyMode = null;
1198 |   this.sweepResult = null;
1199 |   this.sampleValue = null;
1200 |   this.vol = null;
1201 | 
1202 |   this.reset();
1203 | };
1204 | 
1205 | ChannelSquare.prototype = {
1206 |   reset: function () {
1207 |     this.progTimerCount = 0;
1208 |     this.progTimerMax = 0;
1209 |     this.lengthCounter = 0;
1210 |     this.squareCounter = 0;
1211 |     this.sweepCounter = 0;
1212 |     this.sweepCounterMax = 0;
1213 |     this.sweepMode = 0;
1214 |     this.sweepShiftAmount = 0;
1215 |     this.envDecayRate = 0;
1216 |     this.envDecayCounter = 0;
1217 |     this.envVolume = 0;
1218 |     this.masterVolume = 0;
1219 |     this.dutyMode = 0;
1220 |     this.vol = 0;
1221 | 
1222 |     this.isEnabled = false;
1223 |     this.lengthCounterEnable = false;
1224 |     this.sweepActive = false;
1225 |     this.sweepCarry = false;
1226 |     this.envDecayDisable = false;
1227 |     this.envDecayLoopEnable = false;
1228 |   },
1229 | 
1230 |   clockLengthCounter: function () {
1231 |     if (this.lengthCounterEnable && this.lengthCounter > 0) {
1232 |       this.lengthCounter--;
1233 |       if (this.lengthCounter === 0) {
1234 |         this.updateSampleValue();
1235 |       }
1236 |     }
1237 |   },
1238 | 
1239 |   clockEnvDecay: function () {
1240 |     if (this.envReset) {
1241 |       // Reset envelope:
1242 |       this.envReset = false;
1243 |       this.envDecayCounter = this.envDecayRate + 1;
1244 |       this.envVolume = 0xf;
1245 |     } else if (--this.envDecayCounter <= 0) {
1246 |       // Normal handling:
1247 |       this.envDecayCounter = this.envDecayRate + 1;
1248 |       if (this.envVolume > 0) {
1249 |         this.envVolume--;
1250 |       } else {
1251 |         this.envVolume = this.envDecayLoopEnable ? 0xf : 0;
1252 |       }
1253 |     }
1254 | 
1255 |     if (this.envDecayDisable) {
1256 |       this.masterVolume = this.envDecayRate;
1257 |     } else {
1258 |       this.masterVolume = this.envVolume;
1259 |     }
1260 |     this.updateSampleValue();
1261 |   },
1262 | 
1263 |   clockSweep: function () {
1264 |     if (--this.sweepCounter <= 0) {
1265 |       this.sweepCounter = this.sweepCounterMax + 1;
1266 |       if (
1267 |         this.sweepActive &&
1268 |         this.sweepShiftAmount > 0 &&
1269 |         this.progTimerMax > 7
1270 |       ) {
1271 |         // Calculate result from shifter:
1272 |         this.sweepCarry = false;
1273 |         if (this.sweepMode === 0) {
1274 |           this.progTimerMax += this.progTimerMax >> this.sweepShiftAmount;
1275 |           if (this.progTimerMax > 4095) {
1276 |             this.progTimerMax = 4095;
1277 |             this.sweepCarry = true;
1278 |           }
1279 |         } else {
1280 |           this.progTimerMax =
1281 |             this.progTimerMax -
1282 |             ((this.progTimerMax >> this.sweepShiftAmount) -
1283 |               (this.sqr1 ? 1 : 0));
1284 |         }
1285 |       }
1286 |     }
1287 | 
1288 |     if (this.updateSweepPeriod) {
1289 |       this.updateSweepPeriod = false;
1290 |       this.sweepCounter = this.sweepCounterMax + 1;
1291 |     }
1292 |   },
1293 | 
1294 |   updateSampleValue: function () {
1295 |     if (this.isEnabled && this.lengthCounter > 0 && this.progTimerMax > 7) {
1296 |       if (
1297 |         this.sweepMode === 0 &&
1298 |         this.progTimerMax + (this.progTimerMax >> this.sweepShiftAmount) > 4095
1299 |       ) {
1300 |         //if (this.sweepCarry) {
1301 |         this.sampleValue = 0;
1302 |       } else {
1303 |         this.sampleValue =
1304 |           this.masterVolume *
1305 |           this.dutyLookup[(this.dutyMode << 3) + this.squareCounter];
1306 |       }
1307 |     } else {
1308 |       this.sampleValue = 0;
1309 |     }
1310 |   },
1311 | 
1312 |   writeReg: function (address, value) {
1313 |     var addrAdd = this.sqr1 ? 0 : 4;
1314 |     if (address === 0x4000 + addrAdd) {
1315 |       // Volume/Envelope decay:
1316 |       this.envDecayDisable = (value & 0x10) !== 0;
1317 |       this.envDecayRate = value & 0xf;
1318 |       this.envDecayLoopEnable = (value & 0x20) !== 0;
1319 |       this.dutyMode = (value >> 6) & 0x3;
1320 |       this.lengthCounterEnable = (value & 0x20) === 0;
1321 |       if (this.envDecayDisable) {
1322 |         this.masterVolume = this.envDecayRate;
1323 |       } else {
1324 |         this.masterVolume = this.envVolume;
1325 |       }
1326 |       this.updateSampleValue();
1327 |     } else if (address === 0x4001 + addrAdd) {
1328 |       // Sweep:
1329 |       this.sweepActive = (value & 0x80) !== 0;
1330 |       this.sweepCounterMax = (value >> 4) & 7;
1331 |       this.sweepMode = (value >> 3) & 1;
1332 |       this.sweepShiftAmount = value & 7;
1333 |       this.updateSweepPeriod = true;
1334 |     } else if (address === 0x4002 + addrAdd) {
1335 |       // Programmable timer:
1336 |       this.progTimerMax &= 0x700;
1337 |       this.progTimerMax |= value;
1338 |     } else if (address === 0x4003 + addrAdd) {
1339 |       // Programmable timer, length counter
1340 |       this.progTimerMax &= 0xff;
1341 |       this.progTimerMax |= (value & 0x7) << 8;
1342 | 
1343 |       if (this.isEnabled) {
1344 |         this.lengthCounter = this.papu.getLengthMax(value & 0xf8);
1345 |       }
1346 | 
1347 |       this.envReset = true;
1348 |     }
1349 |   },
1350 | 
1351 |   setEnabled: function (value) {
1352 |     this.isEnabled = value;
1353 |     if (!value) {
1354 |       this.lengthCounter = 0;
1355 |     }
1356 |     this.updateSampleValue();
1357 |   },
1358 | 
1359 |   getLengthStatus: function () {
1360 |     return this.lengthCounter === 0 || !this.isEnabled ? 0 : 1;
1361 |   },
1362 | 
1363 |   JSON_PROPERTIES: [
1364 |     "isEnabled",
1365 |     "lengthCounterEnable",
1366 |     "sweepActive",
1367 |     "envDecayDisable",
1368 |     "envDecayLoopEnable",
1369 |     "envReset",
1370 |     "sweepCarry",
1371 |     "updateSweepPeriod",
1372 |     "progTimerCount",
1373 |     "progTimerMax",
1374 |     "lengthCounter",
1375 |     "squareCounter",
1376 |     "sweepCounter",
1377 |     "sweepCounterMax",
1378 |     "sweepMode",
1379 |     "sweepShiftAmount",
1380 |     "envDecayRate",
1381 |     "envDecayCounter",
1382 |     "envVolume",
1383 |     "masterVolume",
1384 |     "dutyMode",
1385 |     "sweepResult",
1386 |     "sampleValue",
1387 |     "vol",
1388 |   ],
1389 | 
1390 |   toJSON: function () {
1391 |     return utils.toJSON(this);
1392 |   },
1393 | 
1394 |   fromJSON: function (s) {
1395 |     utils.fromJSON(this, s);
1396 |   },
1397 | };
1398 | 
1399 | var ChannelTriangle = function (papu) {
1400 |   this.papu = papu;
1401 | 
1402 |   this.isEnabled = null;
1403 |   this.sampleCondition = null;
1404 |   this.lengthCounterEnable = null;
1405 |   this.lcHalt = null;
1406 |   this.lcControl = null;
1407 | 
1408 |   this.progTimerCount = null;
1409 |   this.progTimerMax = null;
1410 |   this.triangleCounter = null;
1411 |   this.lengthCounter = null;
1412 |   this.linearCounter = null;
1413 |   this.lcLoadValue = null;
1414 |   this.sampleValue = null;
1415 |   this.tmp = null;
1416 | 
1417 |   this.reset();
1418 | };
1419 | 
1420 | ChannelTriangle.prototype = {
1421 |   reset: function () {
1422 |     this.progTimerCount = 0;
1423 |     this.progTimerMax = 0;
1424 |     this.triangleCounter = 0;
1425 |     this.isEnabled = false;
1426 |     this.sampleCondition = false;
1427 |     this.lengthCounter = 0;
1428 |     this.lengthCounterEnable = false;
1429 |     this.linearCounter = 0;
1430 |     this.lcLoadValue = 0;
1431 |     this.lcHalt = true;
1432 |     this.lcControl = false;
1433 |     this.tmp = 0;
1434 |     this.sampleValue = 0xf;
1435 |   },
1436 | 
1437 |   clockLengthCounter: function () {
1438 |     if (this.lengthCounterEnable && this.lengthCounter > 0) {
1439 |       this.lengthCounter--;
1440 |       if (this.lengthCounter === 0) {
1441 |         this.updateSampleCondition();
1442 |       }
1443 |     }
1444 |   },
1445 | 
1446 |   clockLinearCounter: function () {
1447 |     if (this.lcHalt) {
1448 |       // Load:
1449 |       this.linearCounter = this.lcLoadValue;
1450 |       this.updateSampleCondition();
1451 |     } else if (this.linearCounter > 0) {
1452 |       // Decrement:
1453 |       this.linearCounter--;
1454 |       this.updateSampleCondition();
1455 |     }
1456 |     if (!this.lcControl) {
1457 |       // Clear halt flag:
1458 |       this.lcHalt = false;
1459 |     }
1460 |   },
1461 | 
1462 |   getLengthStatus: function () {
1463 |     return this.lengthCounter === 0 || !this.isEnabled ? 0 : 1;
1464 |   },
1465 | 
1466 |   // eslint-disable-next-line no-unused-vars
1467 |   readReg: function (address) {
1468 |     return 0;
1469 |   },
1470 | 
1471 |   writeReg: function (address, value) {
1472 |     if (address === 0x4008) {
1473 |       // New values for linear counter:
1474 |       this.lcControl = (value & 0x80) !== 0;
1475 |       this.lcLoadValue = value & 0x7f;
1476 | 
1477 |       // Length counter enable:
1478 |       this.lengthCounterEnable = !this.lcControl;
1479 |     } else if (address === 0x400a) {
1480 |       // Programmable timer:
1481 |       this.progTimerMax &= 0x700;
1482 |       this.progTimerMax |= value;
1483 |     } else if (address === 0x400b) {
1484 |       // Programmable timer, length counter
1485 |       this.progTimerMax &= 0xff;
1486 |       this.progTimerMax |= (value & 0x07) << 8;
1487 |       this.lengthCounter = this.papu.getLengthMax(value & 0xf8);
1488 |       this.lcHalt = true;
1489 |     }
1490 | 
1491 |     this.updateSampleCondition();
1492 |   },
1493 | 
1494 |   clockProgrammableTimer: function (nCycles) {
1495 |     if (this.progTimerMax > 0) {
1496 |       this.progTimerCount += nCycles;
1497 |       while (
1498 |         this.progTimerMax > 0 &&
1499 |         this.progTimerCount >= this.progTimerMax
1500 |       ) {
1501 |         this.progTimerCount -= this.progTimerMax;
1502 |         if (
1503 |           this.isEnabled &&
1504 |           this.lengthCounter > 0 &&
1505 |           this.linearCounter > 0
1506 |         ) {
1507 |           this.clockTriangleGenerator();
1508 |         }
1509 |       }
1510 |     }
1511 |   },
1512 | 
1513 |   clockTriangleGenerator: function () {
1514 |     this.triangleCounter++;
1515 |     this.triangleCounter &= 0x1f;
1516 |   },
1517 | 
1518 |   setEnabled: function (value) {
1519 |     this.isEnabled = value;
1520 |     if (!value) {
1521 |       this.lengthCounter = 0;
1522 |     }
1523 |     this.updateSampleCondition();
1524 |   },
1525 | 
1526 |   updateSampleCondition: function () {
1527 |     this.sampleCondition =
1528 |       this.isEnabled &&
1529 |       this.progTimerMax > 7 &&
1530 |       this.linearCounter > 0 &&
1531 |       this.lengthCounter > 0;
1532 |   },
1533 | 
1534 |   JSON_PROPERTIES: [
1535 |     "isEnabled",
1536 |     "sampleCondition",
1537 |     "lengthCounterEnable",
1538 |     "lcHalt",
1539 |     "lcControl",
1540 |     "progTimerCount",
1541 |     "progTimerMax",
1542 |     "triangleCounter",
1543 |     "lengthCounter",
1544 |     "linearCounter",
1545 |     "lcLoadValue",
1546 |     "sampleValue",
1547 |     "tmp",
1548 |   ],
1549 | 
1550 |   toJSON: function () {
1551 |     return utils.toJSON(this);
1552 |   },
1553 | 
1554 |   fromJSON: function (s) {
1555 |     utils.fromJSON(this, s);
1556 |   },
1557 | };
1558 | 
1559 | module.exports = PAPU;
1560 | 


--------------------------------------------------------------------------------
/src/ppu.js:
--------------------------------------------------------------------------------
   1 | var Tile = require("./tile");
   2 | var utils = require("./utils");
   3 | 
   4 | var PPU = function (nes) {
   5 |   this.nes = nes;
   6 | 
   7 |   // Keep Chrome happy
   8 |   this.vramMem = null;
   9 |   this.spriteMem = null;
  10 |   this.vramAddress = null;
  11 |   this.vramTmpAddress = null;
  12 |   this.vramBufferedReadValue = null;
  13 |   this.firstWrite = null;
  14 |   this.sramAddress = null;
  15 |   this.currentMirroring = null;
  16 |   this.requestEndFrame = null;
  17 |   this.nmiOk = null;
  18 |   this.dummyCycleToggle = null;
  19 |   this.validTileData = null;
  20 |   this.nmiCounter = null;
  21 |   this.scanlineAlreadyRendered = null;
  22 |   this.f_nmiOnVblank = null;
  23 |   this.f_spriteSize = null;
  24 |   this.f_bgPatternTable = null;
  25 |   this.f_spPatternTable = null;
  26 |   this.f_addrInc = null;
  27 |   this.f_nTblAddress = null;
  28 |   this.f_color = null;
  29 |   this.f_spVisibility = null;
  30 |   this.f_bgVisibility = null;
  31 |   this.f_spClipping = null;
  32 |   this.f_bgClipping = null;
  33 |   this.f_dispType = null;
  34 |   this.cntFV = null;
  35 |   this.cntV = null;
  36 |   this.cntH = null;
  37 |   this.cntVT = null;
  38 |   this.cntHT = null;
  39 |   this.regFV = null;
  40 |   this.regV = null;
  41 |   this.regH = null;
  42 |   this.regVT = null;
  43 |   this.regHT = null;
  44 |   this.regFH = null;
  45 |   this.regS = null;
  46 |   this.curNt = null;
  47 |   this.attrib = null;
  48 |   this.buffer = null;
  49 |   this.bgbuffer = null;
  50 |   this.pixrendered = null;
  51 | 
  52 |   this.validTileData = null;
  53 |   this.scantile = null;
  54 |   this.scanline = null;
  55 |   this.lastRenderedScanline = null;
  56 |   this.curX = null;
  57 |   this.sprX = null;
  58 |   this.sprY = null;
  59 |   this.sprTile = null;
  60 |   this.sprCol = null;
  61 |   this.vertFlip = null;
  62 |   this.horiFlip = null;
  63 |   this.bgPriority = null;
  64 |   this.spr0HitX = null;
  65 |   this.spr0HitY = null;
  66 |   this.hitSpr0 = null;
  67 |   this.sprPalette = null;
  68 |   this.imgPalette = null;
  69 |   this.ptTile = null;
  70 |   this.ntable1 = null;
  71 |   this.currentMirroring = null;
  72 |   this.nameTable = null;
  73 |   this.vramMirrorTable = null;
  74 |   this.palTable = null;
  75 | 
  76 |   // Rendering Options:
  77 |   this.showSpr0Hit = false;
  78 |   this.clipToTvSize = true;
  79 | 
  80 |   this.reset();
  81 | };
  82 | 
  83 | PPU.prototype = {
  84 |   // Status flags:
  85 |   STATUS_VRAMWRITE: 4,
  86 |   STATUS_SLSPRITECOUNT: 5,
  87 |   STATUS_SPRITE0HIT: 6,
  88 |   STATUS_VBLANK: 7,
  89 | 
  90 |   reset: function () {
  91 |     var i;
  92 | 
  93 |     // Memory
  94 |     this.vramMem = new Array(0x8000);
  95 |     this.spriteMem = new Array(0x100);
  96 |     for (i = 0; i < this.vramMem.length; i++) {
  97 |       this.vramMem[i] = 0;
  98 |     }
  99 |     for (i = 0; i < this.spriteMem.length; i++) {
 100 |       this.spriteMem[i] = 0;
 101 |     }
 102 | 
 103 |     // VRAM I/O:
 104 |     this.vramAddress = null;
 105 |     this.vramTmpAddress = null;
 106 |     this.vramBufferedReadValue = 0;
 107 |     this.firstWrite = true; // VRAM/Scroll Hi/Lo latch
 108 | 
 109 |     // SPR-RAM I/O:
 110 |     this.sramAddress = 0; // 8-bit only.
 111 | 
 112 |     this.currentMirroring = -1;
 113 |     this.requestEndFrame = false;
 114 |     this.nmiOk = false;
 115 |     this.dummyCycleToggle = false;
 116 |     this.validTileData = false;
 117 |     this.nmiCounter = 0;
 118 |     this.scanlineAlreadyRendered = null;
 119 | 
 120 |     // Control Flags Register 1:
 121 |     this.f_nmiOnVblank = 0; // NMI on VBlank. 0=disable, 1=enable
 122 |     this.f_spriteSize = 0; // Sprite size. 0=8x8, 1=8x16
 123 |     this.f_bgPatternTable = 0; // Background Pattern Table address. 0=0x0000,1=0x1000
 124 |     this.f_spPatternTable = 0; // Sprite Pattern Table address. 0=0x0000,1=0x1000
 125 |     this.f_addrInc = 0; // PPU Address Increment. 0=1,1=32
 126 |     this.f_nTblAddress = 0; // Name Table Address. 0=0x2000,1=0x2400,2=0x2800,3=0x2C00
 127 | 
 128 |     // Control Flags Register 2:
 129 |     this.f_color = 0; // Background color. 0=black, 1=blue, 2=green, 4=red
 130 |     this.f_spVisibility = 0; // Sprite visibility. 0=not displayed,1=displayed
 131 |     this.f_bgVisibility = 0; // Background visibility. 0=Not Displayed,1=displayed
 132 |     this.f_spClipping = 0; // Sprite clipping. 0=Sprites invisible in left 8-pixel column,1=No clipping
 133 |     this.f_bgClipping = 0; // Background clipping. 0=BG invisible in left 8-pixel column, 1=No clipping
 134 |     this.f_dispType = 0; // Display type. 0=color, 1=monochrome
 135 | 
 136 |     // Counters:
 137 |     this.cntFV = 0;
 138 |     this.cntV = 0;
 139 |     this.cntH = 0;
 140 |     this.cntVT = 0;
 141 |     this.cntHT = 0;
 142 | 
 143 |     // Registers:
 144 |     this.regFV = 0;
 145 |     this.regV = 0;
 146 |     this.regH = 0;
 147 |     this.regVT = 0;
 148 |     this.regHT = 0;
 149 |     this.regFH = 0;
 150 |     this.regS = 0;
 151 | 
 152 |     // These are temporary variables used in rendering and sound procedures.
 153 |     // Their states outside of those procedures can be ignored.
 154 |     // TODO: the use of this is a bit weird, investigate
 155 |     this.curNt = null;
 156 | 
 157 |     // Variables used when rendering:
 158 |     this.attrib = new Array(32);
 159 |     this.buffer = new Array(256 * 240);
 160 |     this.bgbuffer = new Array(256 * 240);
 161 |     this.pixrendered = new Array(256 * 240);
 162 | 
 163 |     this.validTileData = null;
 164 | 
 165 |     this.scantile = new Array(32);
 166 | 
 167 |     // Initialize misc vars:
 168 |     this.scanline = 0;
 169 |     this.lastRenderedScanline = -1;
 170 |     this.curX = 0;
 171 | 
 172 |     // Sprite data:
 173 |     this.sprX = new Array(64); // X coordinate
 174 |     this.sprY = new Array(64); // Y coordinate
 175 |     this.sprTile = new Array(64); // Tile Index (into pattern table)
 176 |     this.sprCol = new Array(64); // Upper two bits of color
 177 |     this.vertFlip = new Array(64); // Vertical Flip
 178 |     this.horiFlip = new Array(64); // Horizontal Flip
 179 |     this.bgPriority = new Array(64); // Background priority
 180 |     this.spr0HitX = 0; // Sprite #0 hit X coordinate
 181 |     this.spr0HitY = 0; // Sprite #0 hit Y coordinate
 182 |     this.hitSpr0 = false;
 183 | 
 184 |     // Palette data:
 185 |     this.sprPalette = new Array(16);
 186 |     this.imgPalette = new Array(16);
 187 | 
 188 |     // Create pattern table tile buffers:
 189 |     this.ptTile = new Array(512);
 190 |     for (i = 0; i < 512; i++) {
 191 |       this.ptTile[i] = new Tile();
 192 |     }
 193 | 
 194 |     // Create nametable buffers:
 195 |     // Name table data:
 196 |     this.ntable1 = new Array(4);
 197 |     this.currentMirroring = -1;
 198 |     this.nameTable = new Array(4);
 199 |     for (i = 0; i < 4; i++) {
 200 |       this.nameTable[i] = new NameTable(32, 32, "Nt" + i);
 201 |     }
 202 | 
 203 |     // Initialize mirroring lookup table:
 204 |     this.vramMirrorTable = new Array(0x8000);
 205 |     for (i = 0; i < 0x8000; i++) {
 206 |       this.vramMirrorTable[i] = i;
 207 |     }
 208 | 
 209 |     this.palTable = new PaletteTable();
 210 |     this.palTable.loadNTSCPalette();
 211 |     //this.palTable.loadDefaultPalette();
 212 | 
 213 |     this.updateControlReg1(0);
 214 |     this.updateControlReg2(0);
 215 |   },
 216 | 
 217 |   // Sets Nametable mirroring.
 218 |   setMirroring: function (mirroring) {
 219 |     if (mirroring === this.currentMirroring) {
 220 |       return;
 221 |     }
 222 | 
 223 |     this.currentMirroring = mirroring;
 224 |     this.triggerRendering();
 225 | 
 226 |     // Remove mirroring:
 227 |     if (this.vramMirrorTable === null) {
 228 |       this.vramMirrorTable = new Array(0x8000);
 229 |     }
 230 |     for (var i = 0; i < 0x8000; i++) {
 231 |       this.vramMirrorTable[i] = i;
 232 |     }
 233 | 
 234 |     // Palette mirroring:
 235 |     this.defineMirrorRegion(0x3f20, 0x3f00, 0x20);
 236 |     this.defineMirrorRegion(0x3f40, 0x3f00, 0x20);
 237 |     this.defineMirrorRegion(0x3f80, 0x3f00, 0x20);
 238 |     this.defineMirrorRegion(0x3fc0, 0x3f00, 0x20);
 239 | 
 240 |     // Additional mirroring:
 241 |     this.defineMirrorRegion(0x3000, 0x2000, 0xf00);
 242 |     this.defineMirrorRegion(0x4000, 0x0000, 0x4000);
 243 | 
 244 |     if (mirroring === this.nes.rom.HORIZONTAL_MIRRORING) {
 245 |       // Horizontal mirroring.
 246 | 
 247 |       this.ntable1[0] = 0;
 248 |       this.ntable1[1] = 0;
 249 |       this.ntable1[2] = 1;
 250 |       this.ntable1[3] = 1;
 251 | 
 252 |       this.defineMirrorRegion(0x2400, 0x2000, 0x400);
 253 |       this.defineMirrorRegion(0x2c00, 0x2800, 0x400);
 254 |     } else if (mirroring === this.nes.rom.VERTICAL_MIRRORING) {
 255 |       // Vertical mirroring.
 256 | 
 257 |       this.ntable1[0] = 0;
 258 |       this.ntable1[1] = 1;
 259 |       this.ntable1[2] = 0;
 260 |       this.ntable1[3] = 1;
 261 | 
 262 |       this.defineMirrorRegion(0x2800, 0x2000, 0x400);
 263 |       this.defineMirrorRegion(0x2c00, 0x2400, 0x400);
 264 |     } else if (mirroring === this.nes.rom.SINGLESCREEN_MIRRORING) {
 265 |       // Single Screen mirroring
 266 | 
 267 |       this.ntable1[0] = 0;
 268 |       this.ntable1[1] = 0;
 269 |       this.ntable1[2] = 0;
 270 |       this.ntable1[3] = 0;
 271 | 
 272 |       this.defineMirrorRegion(0x2400, 0x2000, 0x400);
 273 |       this.defineMirrorRegion(0x2800, 0x2000, 0x400);
 274 |       this.defineMirrorRegion(0x2c00, 0x2000, 0x400);
 275 |     } else if (mirroring === this.nes.rom.SINGLESCREEN_MIRRORING2) {
 276 |       this.ntable1[0] = 1;
 277 |       this.ntable1[1] = 1;
 278 |       this.ntable1[2] = 1;
 279 |       this.ntable1[3] = 1;
 280 | 
 281 |       this.defineMirrorRegion(0x2400, 0x2400, 0x400);
 282 |       this.defineMirrorRegion(0x2800, 0x2400, 0x400);
 283 |       this.defineMirrorRegion(0x2c00, 0x2400, 0x400);
 284 |     } else {
 285 |       // Assume Four-screen mirroring.
 286 | 
 287 |       this.ntable1[0] = 0;
 288 |       this.ntable1[1] = 1;
 289 |       this.ntable1[2] = 2;
 290 |       this.ntable1[3] = 3;
 291 |     }
 292 |   },
 293 | 
 294 |   // Define a mirrored area in the address lookup table.
 295 |   // Assumes the regions don't overlap.
 296 |   // The 'to' region is the region that is physically in memory.
 297 |   defineMirrorRegion: function (fromStart, toStart, size) {
 298 |     for (var i = 0; i < size; i++) {
 299 |       this.vramMirrorTable[fromStart + i] = toStart + i;
 300 |     }
 301 |   },
 302 | 
 303 |   startVBlank: function () {
 304 |     // Do NMI:
 305 |     this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NMI);
 306 | 
 307 |     // Make sure everything is rendered:
 308 |     if (this.lastRenderedScanline < 239) {
 309 |       this.renderFramePartially(
 310 |         this.lastRenderedScanline + 1,
 311 |         240 - this.lastRenderedScanline
 312 |       );
 313 |     }
 314 | 
 315 |     // End frame:
 316 |     this.endFrame();
 317 | 
 318 |     // Reset scanline counter:
 319 |     this.lastRenderedScanline = -1;
 320 |   },
 321 | 
 322 |   endScanline: function () {
 323 |     switch (this.scanline) {
 324 |       case 19:
 325 |         // Dummy scanline.
 326 |         // May be variable length:
 327 |         if (this.dummyCycleToggle) {
 328 |           // Remove dead cycle at end of scanline,
 329 |           // for next scanline:
 330 |           this.curX = 1;
 331 |           this.dummyCycleToggle = !this.dummyCycleToggle;
 332 |         }
 333 |         break;
 334 | 
 335 |       case 20:
 336 |         // Clear VBlank flag:
 337 |         this.setStatusFlag(this.STATUS_VBLANK, false);
 338 | 
 339 |         // Clear Sprite #0 hit flag:
 340 |         this.setStatusFlag(this.STATUS_SPRITE0HIT, false);
 341 |         this.hitSpr0 = false;
 342 |         this.spr0HitX = -1;
 343 |         this.spr0HitY = -1;
 344 | 
 345 |         if (this.f_bgVisibility === 1 || this.f_spVisibility === 1) {
 346 |           // Update counters:
 347 |           this.cntFV = this.regFV;
 348 |           this.cntV = this.regV;
 349 |           this.cntH = this.regH;
 350 |           this.cntVT = this.regVT;
 351 |           this.cntHT = this.regHT;
 352 | 
 353 |           if (this.f_bgVisibility === 1) {
 354 |             // Render dummy scanline:
 355 |             this.renderBgScanline(false, 0);
 356 |           }
 357 |         }
 358 | 
 359 |         if (this.f_bgVisibility === 1 && this.f_spVisibility === 1) {
 360 |           // Check sprite 0 hit for first scanline:
 361 |           this.checkSprite0(0);
 362 |         }
 363 | 
 364 |         if (this.f_bgVisibility === 1 || this.f_spVisibility === 1) {
 365 |           // Clock mapper IRQ Counter:
 366 |           this.nes.mmap.clockIrqCounter();
 367 |         }
 368 |         break;
 369 | 
 370 |       case 261:
 371 |         // Dead scanline, no rendering.
 372 |         // Set VINT:
 373 |         this.setStatusFlag(this.STATUS_VBLANK, true);
 374 |         this.requestEndFrame = true;
 375 |         this.nmiCounter = 9;
 376 | 
 377 |         // Wrap around:
 378 |         this.scanline = -1; // will be incremented to 0
 379 | 
 380 |         break;
 381 | 
 382 |       default:
 383 |         if (this.scanline >= 21 && this.scanline <= 260) {
 384 |           // Render normally:
 385 |           if (this.f_bgVisibility === 1) {
 386 |             if (!this.scanlineAlreadyRendered) {
 387 |               // update scroll:
 388 |               this.cntHT = this.regHT;
 389 |               this.cntH = this.regH;
 390 |               this.renderBgScanline(true, this.scanline + 1 - 21);
 391 |             }
 392 |             this.scanlineAlreadyRendered = false;
 393 | 
 394 |             // Check for sprite 0 (next scanline):
 395 |             if (!this.hitSpr0 && this.f_spVisibility === 1) {
 396 |               if (
 397 |                 this.sprX[0] >= -7 &&
 398 |                 this.sprX[0] < 256 &&
 399 |                 this.sprY[0] + 1 <= this.scanline - 20 &&
 400 |                 this.sprY[0] + 1 + (this.f_spriteSize === 0 ? 8 : 16) >=
 401 |                   this.scanline - 20
 402 |               ) {
 403 |                 if (this.checkSprite0(this.scanline - 20)) {
 404 |                   this.hitSpr0 = true;
 405 |                 }
 406 |               }
 407 |             }
 408 |           }
 409 | 
 410 |           if (this.f_bgVisibility === 1 || this.f_spVisibility === 1) {
 411 |             // Clock mapper IRQ Counter:
 412 |             this.nes.mmap.clockIrqCounter();
 413 |           }
 414 |         }
 415 |     }
 416 | 
 417 |     this.scanline++;
 418 |     this.regsToAddress();
 419 |     this.cntsToAddress();
 420 |   },
 421 | 
 422 |   startFrame: function () {
 423 |     // Set background color:
 424 |     var bgColor = 0;
 425 | 
 426 |     if (this.f_dispType === 0) {
 427 |       // Color display.
 428 |       // f_color determines color emphasis.
 429 |       // Use first entry of image palette as BG color.
 430 |       bgColor = this.imgPalette[0];
 431 |     } else {
 432 |       // Monochrome display.
 433 |       // f_color determines the bg color.
 434 |       switch (this.f_color) {
 435 |         case 0:
 436 |           // Black
 437 |           bgColor = 0x00000;
 438 |           break;
 439 |         case 1:
 440 |           // Green
 441 |           bgColor = 0x00ff00;
 442 |           break;
 443 |         case 2:
 444 |           // Blue
 445 |           bgColor = 0xff0000;
 446 |           break;
 447 |         case 3:
 448 |           // Invalid. Use black.
 449 |           bgColor = 0x000000;
 450 |           break;
 451 |         case 4:
 452 |           // Red
 453 |           bgColor = 0x0000ff;
 454 |           break;
 455 |         default:
 456 |           // Invalid. Use black.
 457 |           bgColor = 0x0;
 458 |       }
 459 |     }
 460 | 
 461 |     var buffer = this.buffer;
 462 |     var i;
 463 |     for (i = 0; i < 256 * 240; i++) {
 464 |       buffer[i] = bgColor;
 465 |     }
 466 |     var pixrendered = this.pixrendered;
 467 |     for (i = 0; i < pixrendered.length; i++) {
 468 |       pixrendered[i] = 65;
 469 |     }
 470 |   },
 471 | 
 472 |   endFrame: function () {
 473 |     var i, x, y;
 474 |     var buffer = this.buffer;
 475 | 
 476 |     // Draw spr#0 hit coordinates:
 477 |     if (this.showSpr0Hit) {
 478 |       // Spr 0 position:
 479 |       if (
 480 |         this.sprX[0] >= 0 &&
 481 |         this.sprX[0] < 256 &&
 482 |         this.sprY[0] >= 0 &&
 483 |         this.sprY[0] < 240
 484 |       ) {
 485 |         for (i = 0; i < 256; i++) {
 486 |           buffer[(this.sprY[0] << 8) + i] = 0xff5555;
 487 |         }
 488 |         for (i = 0; i < 240; i++) {
 489 |           buffer[(i << 8) + this.sprX[0]] = 0xff5555;
 490 |         }
 491 |       }
 492 |       // Hit position:
 493 |       if (
 494 |         this.spr0HitX >= 0 &&
 495 |         this.spr0HitX < 256 &&
 496 |         this.spr0HitY >= 0 &&
 497 |         this.spr0HitY < 240
 498 |       ) {
 499 |         for (i = 0; i < 256; i++) {
 500 |           buffer[(this.spr0HitY << 8) + i] = 0x55ff55;
 501 |         }
 502 |         for (i = 0; i < 240; i++) {
 503 |           buffer[(i << 8) + this.spr0HitX] = 0x55ff55;
 504 |         }
 505 |       }
 506 |     }
 507 | 
 508 |     // This is a bit lazy..
 509 |     // if either the sprites or the background should be clipped,
 510 |     // both are clipped after rendering is finished.
 511 |     if (
 512 |       this.clipToTvSize ||
 513 |       this.f_bgClipping === 0 ||
 514 |       this.f_spClipping === 0
 515 |     ) {
 516 |       // Clip left 8-pixels column:
 517 |       for (y = 0; y < 240; y++) {
 518 |         for (x = 0; x < 8; x++) {
 519 |           buffer[(y << 8) + x] = 0;
 520 |         }
 521 |       }
 522 |     }
 523 | 
 524 |     if (this.clipToTvSize) {
 525 |       // Clip right 8-pixels column too:
 526 |       for (y = 0; y < 240; y++) {
 527 |         for (x = 0; x < 8; x++) {
 528 |           buffer[(y << 8) + 255 - x] = 0;
 529 |         }
 530 |       }
 531 |     }
 532 | 
 533 |     // Clip top and bottom 8 pixels:
 534 |     if (this.clipToTvSize) {
 535 |       for (y = 0; y < 8; y++) {
 536 |         for (x = 0; x < 256; x++) {
 537 |           buffer[(y << 8) + x] = 0;
 538 |           buffer[((239 - y) << 8) + x] = 0;
 539 |         }
 540 |       }
 541 |     }
 542 | 
 543 |     this.nes.ui.writeFrame(buffer);
 544 |   },
 545 | 
 546 |   updateControlReg1: function (value) {
 547 |     this.triggerRendering();
 548 | 
 549 |     this.f_nmiOnVblank = (value >> 7) & 1;
 550 |     this.f_spriteSize = (value >> 5) & 1;
 551 |     this.f_bgPatternTable = (value >> 4) & 1;
 552 |     this.f_spPatternTable = (value >> 3) & 1;
 553 |     this.f_addrInc = (value >> 2) & 1;
 554 |     this.f_nTblAddress = value & 3;
 555 | 
 556 |     this.regV = (value >> 1) & 1;
 557 |     this.regH = value & 1;
 558 |     this.regS = (value >> 4) & 1;
 559 |   },
 560 | 
 561 |   updateControlReg2: function (value) {
 562 |     this.triggerRendering();
 563 | 
 564 |     this.f_color = (value >> 5) & 7;
 565 |     this.f_spVisibility = (value >> 4) & 1;
 566 |     this.f_bgVisibility = (value >> 3) & 1;
 567 |     this.f_spClipping = (value >> 2) & 1;
 568 |     this.f_bgClipping = (value >> 1) & 1;
 569 |     this.f_dispType = value & 1;
 570 | 
 571 |     if (this.f_dispType === 0) {
 572 |       this.palTable.setEmphasis(this.f_color);
 573 |     }
 574 |     this.updatePalettes();
 575 |   },
 576 | 
 577 |   setStatusFlag: function (flag, value) {
 578 |     var n = 1 << flag;
 579 |     this.nes.cpu.mem[0x2002] =
 580 |       (this.nes.cpu.mem[0x2002] & (255 - n)) | (value ? n : 0);
 581 |   },
 582 | 
 583 |   // CPU Register $2002:
 584 |   // Read the Status Register.
 585 |   readStatusRegister: function () {
 586 |     var tmp = this.nes.cpu.mem[0x2002];
 587 | 
 588 |     // Reset scroll & VRAM Address toggle:
 589 |     this.firstWrite = true;
 590 | 
 591 |     // Clear VBlank flag:
 592 |     this.setStatusFlag(this.STATUS_VBLANK, false);
 593 | 
 594 |     // Fetch status data:
 595 |     return tmp;
 596 |   },
 597 | 
 598 |   // CPU Register $2003:
 599 |   // Write the SPR-RAM address that is used for sramWrite (Register 0x2004 in CPU memory map)
 600 |   writeSRAMAddress: function (address) {
 601 |     this.sramAddress = address;
 602 |   },
 603 | 
 604 |   // CPU Register $2004 (R):
 605 |   // Read from SPR-RAM (Sprite RAM).
 606 |   // The address should be set first.
 607 |   sramLoad: function () {
 608 |     /*short tmp = sprMem.load(sramAddress);
 609 |         sramAddress++; // Increment address
 610 |         sramAddress%=0x100;
 611 |         return tmp;*/
 612 |     return this.spriteMem[this.sramAddress];
 613 |   },
 614 | 
 615 |   // CPU Register $2004 (W):
 616 |   // Write to SPR-RAM (Sprite RAM).
 617 |   // The address should be set first.
 618 |   sramWrite: function (value) {
 619 |     this.spriteMem[this.sramAddress] = value;
 620 |     this.spriteRamWriteUpdate(this.sramAddress, value);
 621 |     this.sramAddress++; // Increment address
 622 |     this.sramAddress %= 0x100;
 623 |   },
 624 | 
 625 |   // CPU Register $2005:
 626 |   // Write to scroll registers.
 627 |   // The first write is the vertical offset, the second is the
 628 |   // horizontal offset:
 629 |   scrollWrite: function (value) {
 630 |     this.triggerRendering();
 631 | 
 632 |     if (this.firstWrite) {
 633 |       // First write, horizontal scroll:
 634 |       this.regHT = (value >> 3) & 31;
 635 |       this.regFH = value & 7;
 636 |     } else {
 637 |       // Second write, vertical scroll:
 638 |       this.regFV = value & 7;
 639 |       this.regVT = (value >> 3) & 31;
 640 |     }
 641 |     this.firstWrite = !this.firstWrite;
 642 |   },
 643 | 
 644 |   // CPU Register $2006:
 645 |   // Sets the adress used when reading/writing from/to VRAM.
 646 |   // The first write sets the high byte, the second the low byte.
 647 |   writeVRAMAddress: function (address) {
 648 |     if (this.firstWrite) {
 649 |       this.regFV = (address >> 4) & 3;
 650 |       this.regV = (address >> 3) & 1;
 651 |       this.regH = (address >> 2) & 1;
 652 |       this.regVT = (this.regVT & 7) | ((address & 3) << 3);
 653 |     } else {
 654 |       this.triggerRendering();
 655 | 
 656 |       this.regVT = (this.regVT & 24) | ((address >> 5) & 7);
 657 |       this.regHT = address & 31;
 658 | 
 659 |       this.cntFV = this.regFV;
 660 |       this.cntV = this.regV;
 661 |       this.cntH = this.regH;
 662 |       this.cntVT = this.regVT;
 663 |       this.cntHT = this.regHT;
 664 | 
 665 |       this.checkSprite0(this.scanline - 20);
 666 |     }
 667 | 
 668 |     this.firstWrite = !this.firstWrite;
 669 | 
 670 |     // Invoke mapper latch:
 671 |     this.cntsToAddress();
 672 |     if (this.vramAddress < 0x2000) {
 673 |       this.nes.mmap.latchAccess(this.vramAddress);
 674 |     }
 675 |   },
 676 | 
 677 |   // CPU Register $2007(R):
 678 |   // Read from PPU memory. The address should be set first.
 679 |   vramLoad: function () {
 680 |     var tmp;
 681 | 
 682 |     this.cntsToAddress();
 683 |     this.regsToAddress();
 684 | 
 685 |     // If address is in range 0x0000-0x3EFF, return buffered values:
 686 |     if (this.vramAddress <= 0x3eff) {
 687 |       tmp = this.vramBufferedReadValue;
 688 | 
 689 |       // Update buffered value:
 690 |       if (this.vramAddress < 0x2000) {
 691 |         this.vramBufferedReadValue = this.vramMem[this.vramAddress];
 692 |       } else {
 693 |         this.vramBufferedReadValue = this.mirroredLoad(this.vramAddress);
 694 |       }
 695 | 
 696 |       // Mapper latch access:
 697 |       if (this.vramAddress < 0x2000) {
 698 |         this.nes.mmap.latchAccess(this.vramAddress);
 699 |       }
 700 | 
 701 |       // Increment by either 1 or 32, depending on d2 of Control Register 1:
 702 |       this.vramAddress += this.f_addrInc === 1 ? 32 : 1;
 703 | 
 704 |       this.cntsFromAddress();
 705 |       this.regsFromAddress();
 706 | 
 707 |       return tmp; // Return the previous buffered value.
 708 |     }
 709 | 
 710 |     // No buffering in this mem range. Read normally.
 711 |     tmp = this.mirroredLoad(this.vramAddress);
 712 | 
 713 |     // Increment by either 1 or 32, depending on d2 of Control Register 1:
 714 |     this.vramAddress += this.f_addrInc === 1 ? 32 : 1;
 715 | 
 716 |     this.cntsFromAddress();
 717 |     this.regsFromAddress();
 718 | 
 719 |     return tmp;
 720 |   },
 721 | 
 722 |   // CPU Register $2007(W):
 723 |   // Write to PPU memory. The address should be set first.
 724 |   vramWrite: function (value) {
 725 |     this.triggerRendering();
 726 |     this.cntsToAddress();
 727 |     this.regsToAddress();
 728 | 
 729 |     if (this.vramAddress >= 0x2000) {
 730 |       // Mirroring is used.
 731 |       this.mirroredWrite(this.vramAddress, value);
 732 |     } else {
 733 |       // Write normally.
 734 |       this.writeMem(this.vramAddress, value);
 735 | 
 736 |       // Invoke mapper latch:
 737 |       this.nes.mmap.latchAccess(this.vramAddress);
 738 |     }
 739 | 
 740 |     // Increment by either 1 or 32, depending on d2 of Control Register 1:
 741 |     this.vramAddress += this.f_addrInc === 1 ? 32 : 1;
 742 |     this.regsFromAddress();
 743 |     this.cntsFromAddress();
 744 |   },
 745 | 
 746 |   // CPU Register $4014:
 747 |   // Write 256 bytes of main memory
 748 |   // into Sprite RAM.
 749 |   sramDMA: function (value) {
 750 |     var baseAddress = value * 0x100;
 751 |     var data;
 752 |     for (var i = this.sramAddress; i < 256; i++) {
 753 |       data = this.nes.cpu.mem[baseAddress + i];
 754 |       this.spriteMem[i] = data;
 755 |       this.spriteRamWriteUpdate(i, data);
 756 |     }
 757 | 
 758 |     this.nes.cpu.haltCycles(513);
 759 |   },
 760 | 
 761 |   // Updates the scroll registers from a new VRAM address.
 762 |   regsFromAddress: function () {
 763 |     var address = (this.vramTmpAddress >> 8) & 0xff;
 764 |     this.regFV = (address >> 4) & 7;
 765 |     this.regV = (address >> 3) & 1;
 766 |     this.regH = (address >> 2) & 1;
 767 |     this.regVT = (this.regVT & 7) | ((address & 3) << 3);
 768 | 
 769 |     address = this.vramTmpAddress & 0xff;
 770 |     this.regVT = (this.regVT & 24) | ((address >> 5) & 7);
 771 |     this.regHT = address & 31;
 772 |   },
 773 | 
 774 |   // Updates the scroll registers from a new VRAM address.
 775 |   cntsFromAddress: function () {
 776 |     var address = (this.vramAddress >> 8) & 0xff;
 777 |     this.cntFV = (address >> 4) & 3;
 778 |     this.cntV = (address >> 3) & 1;
 779 |     this.cntH = (address >> 2) & 1;
 780 |     this.cntVT = (this.cntVT & 7) | ((address & 3) << 3);
 781 | 
 782 |     address = this.vramAddress & 0xff;
 783 |     this.cntVT = (this.cntVT & 24) | ((address >> 5) & 7);
 784 |     this.cntHT = address & 31;
 785 |   },
 786 | 
 787 |   regsToAddress: function () {
 788 |     var b1 = (this.regFV & 7) << 4;
 789 |     b1 |= (this.regV & 1) << 3;
 790 |     b1 |= (this.regH & 1) << 2;
 791 |     b1 |= (this.regVT >> 3) & 3;
 792 | 
 793 |     var b2 = (this.regVT & 7) << 5;
 794 |     b2 |= this.regHT & 31;
 795 | 
 796 |     this.vramTmpAddress = ((b1 << 8) | b2) & 0x7fff;
 797 |   },
 798 | 
 799 |   cntsToAddress: function () {
 800 |     var b1 = (this.cntFV & 7) << 4;
 801 |     b1 |= (this.cntV & 1) << 3;
 802 |     b1 |= (this.cntH & 1) << 2;
 803 |     b1 |= (this.cntVT >> 3) & 3;
 804 | 
 805 |     var b2 = (this.cntVT & 7) << 5;
 806 |     b2 |= this.cntHT & 31;
 807 | 
 808 |     this.vramAddress = ((b1 << 8) | b2) & 0x7fff;
 809 |   },
 810 | 
 811 |   incTileCounter: function (count) {
 812 |     for (var i = count; i !== 0; i--) {
 813 |       this.cntHT++;
 814 |       if (this.cntHT === 32) {
 815 |         this.cntHT = 0;
 816 |         this.cntVT++;
 817 |         if (this.cntVT >= 30) {
 818 |           this.cntH++;
 819 |           if (this.cntH === 2) {
 820 |             this.cntH = 0;
 821 |             this.cntV++;
 822 |             if (this.cntV === 2) {
 823 |               this.cntV = 0;
 824 |               this.cntFV++;
 825 |               this.cntFV &= 0x7;
 826 |             }
 827 |           }
 828 |         }
 829 |       }
 830 |     }
 831 |   },
 832 | 
 833 |   // Reads from memory, taking into account
 834 |   // mirroring/mapping of address ranges.
 835 |   mirroredLoad: function (address) {
 836 |     return this.vramMem[this.vramMirrorTable[address]];
 837 |   },
 838 | 
 839 |   // Writes to memory, taking into account
 840 |   // mirroring/mapping of address ranges.
 841 |   mirroredWrite: function (address, value) {
 842 |     if (address >= 0x3f00 && address < 0x3f20) {
 843 |       // Palette write mirroring.
 844 |       if (address === 0x3f00 || address === 0x3f10) {
 845 |         this.writeMem(0x3f00, value);
 846 |         this.writeMem(0x3f10, value);
 847 |       } else if (address === 0x3f04 || address === 0x3f14) {
 848 |         this.writeMem(0x3f04, value);
 849 |         this.writeMem(0x3f14, value);
 850 |       } else if (address === 0x3f08 || address === 0x3f18) {
 851 |         this.writeMem(0x3f08, value);
 852 |         this.writeMem(0x3f18, value);
 853 |       } else if (address === 0x3f0c || address === 0x3f1c) {
 854 |         this.writeMem(0x3f0c, value);
 855 |         this.writeMem(0x3f1c, value);
 856 |       } else {
 857 |         this.writeMem(address, value);
 858 |       }
 859 |     } else {
 860 |       // Use lookup table for mirrored address:
 861 |       if (address < this.vramMirrorTable.length) {
 862 |         this.writeMem(this.vramMirrorTable[address], value);
 863 |       } else {
 864 |         throw new Error("Invalid VRAM address: " + address.toString(16));
 865 |       }
 866 |     }
 867 |   },
 868 | 
 869 |   triggerRendering: function () {
 870 |     if (this.scanline >= 21 && this.scanline <= 260) {
 871 |       // Render sprites, and combine:
 872 |       this.renderFramePartially(
 873 |         this.lastRenderedScanline + 1,
 874 |         this.scanline - 21 - this.lastRenderedScanline
 875 |       );
 876 | 
 877 |       // Set last rendered scanline:
 878 |       this.lastRenderedScanline = this.scanline - 21;
 879 |     }
 880 |   },
 881 | 
 882 |   renderFramePartially: function (startScan, scanCount) {
 883 |     if (this.f_spVisibility === 1) {
 884 |       this.renderSpritesPartially(startScan, scanCount, true);
 885 |     }
 886 | 
 887 |     if (this.f_bgVisibility === 1) {
 888 |       var si = startScan << 8;
 889 |       var ei = (startScan + scanCount) << 8;
 890 |       if (ei > 0xf000) {
 891 |         ei = 0xf000;
 892 |       }
 893 |       var buffer = this.buffer;
 894 |       var bgbuffer = this.bgbuffer;
 895 |       var pixrendered = this.pixrendered;
 896 |       for (var destIndex = si; destIndex < ei; destIndex++) {
 897 |         if (pixrendered[destIndex] > 0xff) {
 898 |           buffer[destIndex] = bgbuffer[destIndex];
 899 |         }
 900 |       }
 901 |     }
 902 | 
 903 |     if (this.f_spVisibility === 1) {
 904 |       this.renderSpritesPartially(startScan, scanCount, false);
 905 |     }
 906 | 
 907 |     this.validTileData = false;
 908 |   },
 909 | 
 910 |   renderBgScanline: function (bgbuffer, scan) {
 911 |     var baseTile = this.regS === 0 ? 0 : 256;
 912 |     var destIndex = (scan << 8) - this.regFH;
 913 | 
 914 |     this.curNt = this.ntable1[this.cntV + this.cntV + this.cntH];
 915 | 
 916 |     this.cntHT = this.regHT;
 917 |     this.cntH = this.regH;
 918 |     this.curNt = this.ntable1[this.cntV + this.cntV + this.cntH];
 919 | 
 920 |     if (scan < 240 && scan - this.cntFV >= 0) {
 921 |       var tscanoffset = this.cntFV << 3;
 922 |       var scantile = this.scantile;
 923 |       var attrib = this.attrib;
 924 |       var ptTile = this.ptTile;
 925 |       var nameTable = this.nameTable;
 926 |       var imgPalette = this.imgPalette;
 927 |       var pixrendered = this.pixrendered;
 928 |       var targetBuffer = bgbuffer ? this.bgbuffer : this.buffer;
 929 | 
 930 |       var t, tpix, att, col;
 931 | 
 932 |       for (var tile = 0; tile < 32; tile++) {
 933 |         if (scan >= 0) {
 934 |           // Fetch tile & attrib data:
 935 |           if (this.validTileData) {
 936 |             // Get data from array:
 937 |             t = scantile[tile];
 938 |             if (typeof t === "undefined") {
 939 |               continue;
 940 |             }
 941 |             tpix = t.pix;
 942 |             att = attrib[tile];
 943 |           } else {
 944 |             // Fetch data:
 945 |             t =
 946 |               ptTile[
 947 |                 baseTile +
 948 |                   nameTable[this.curNt].getTileIndex(this.cntHT, this.cntVT)
 949 |               ];
 950 |             if (typeof t === "undefined") {
 951 |               continue;
 952 |             }
 953 |             tpix = t.pix;
 954 |             att = nameTable[this.curNt].getAttrib(this.cntHT, this.cntVT);
 955 |             scantile[tile] = t;
 956 |             attrib[tile] = att;
 957 |           }
 958 | 
 959 |           // Render tile scanline:
 960 |           var sx = 0;
 961 |           var x = (tile << 3) - this.regFH;
 962 | 
 963 |           if (x > -8) {
 964 |             if (x < 0) {
 965 |               destIndex -= x;
 966 |               sx = -x;
 967 |             }
 968 |             if (t.opaque[this.cntFV]) {
 969 |               for (; sx < 8; sx++) {
 970 |                 targetBuffer[destIndex] =
 971 |                   imgPalette[tpix[tscanoffset + sx] + att];
 972 |                 pixrendered[destIndex] |= 256;
 973 |                 destIndex++;
 974 |               }
 975 |             } else {
 976 |               for (; sx < 8; sx++) {
 977 |                 col = tpix[tscanoffset + sx];
 978 |                 if (col !== 0) {
 979 |                   targetBuffer[destIndex] = imgPalette[col + att];
 980 |                   pixrendered[destIndex] |= 256;
 981 |                 }
 982 |                 destIndex++;
 983 |               }
 984 |             }
 985 |           }
 986 |         }
 987 | 
 988 |         // Increase Horizontal Tile Counter:
 989 |         if (++this.cntHT === 32) {
 990 |           this.cntHT = 0;
 991 |           this.cntH++;
 992 |           this.cntH %= 2;
 993 |           this.curNt = this.ntable1[(this.cntV << 1) + this.cntH];
 994 |         }
 995 |       }
 996 | 
 997 |       // Tile data for one row should now have been fetched,
 998 |       // so the data in the array is valid.
 999 |       this.validTileData = true;
1000 |     }
1001 | 
1002 |     // update vertical scroll:
1003 |     this.cntFV++;
1004 |     if (this.cntFV === 8) {
1005 |       this.cntFV = 0;
1006 |       this.cntVT++;
1007 |       if (this.cntVT === 30) {
1008 |         this.cntVT = 0;
1009 |         this.cntV++;
1010 |         this.cntV %= 2;
1011 |         this.curNt = this.ntable1[(this.cntV << 1) + this.cntH];
1012 |       } else if (this.cntVT === 32) {
1013 |         this.cntVT = 0;
1014 |       }
1015 | 
1016 |       // Invalidate fetched data:
1017 |       this.validTileData = false;
1018 |     }
1019 |   },
1020 | 
1021 |   renderSpritesPartially: function (startscan, scancount, bgPri) {
1022 |     if (this.f_spVisibility === 1) {
1023 |       for (var i = 0; i < 64; i++) {
1024 |         if (
1025 |           this.bgPriority[i] === bgPri &&
1026 |           this.sprX[i] >= 0 &&
1027 |           this.sprX[i] < 256 &&
1028 |           this.sprY[i] + 8 >= startscan &&
1029 |           this.sprY[i] < startscan + scancount
1030 |         ) {
1031 |           // Show sprite.
1032 |           if (this.f_spriteSize === 0) {
1033 |             // 8x8 sprites
1034 | 
1035 |             this.srcy1 = 0;
1036 |             this.srcy2 = 8;
1037 | 
1038 |             if (this.sprY[i] < startscan) {
1039 |               this.srcy1 = startscan - this.sprY[i] - 1;
1040 |             }
1041 | 
1042 |             if (this.sprY[i] + 8 > startscan + scancount) {
1043 |               this.srcy2 = startscan + scancount - this.sprY[i] + 1;
1044 |             }
1045 | 
1046 |             if (this.f_spPatternTable === 0) {
1047 |               this.ptTile[this.sprTile[i]].render(
1048 |                 this.buffer,
1049 |                 0,
1050 |                 this.srcy1,
1051 |                 8,
1052 |                 this.srcy2,
1053 |                 this.sprX[i],
1054 |                 this.sprY[i] + 1,
1055 |                 this.sprCol[i],
1056 |                 this.sprPalette,
1057 |                 this.horiFlip[i],
1058 |                 this.vertFlip[i],
1059 |                 i,
1060 |                 this.pixrendered
1061 |               );
1062 |             } else {
1063 |               this.ptTile[this.sprTile[i] + 256].render(
1064 |                 this.buffer,
1065 |                 0,
1066 |                 this.srcy1,
1067 |                 8,
1068 |                 this.srcy2,
1069 |                 this.sprX[i],
1070 |                 this.sprY[i] + 1,
1071 |                 this.sprCol[i],
1072 |                 this.sprPalette,
1073 |                 this.horiFlip[i],
1074 |                 this.vertFlip[i],
1075 |                 i,
1076 |                 this.pixrendered
1077 |               );
1078 |             }
1079 |           } else {
1080 |             // 8x16 sprites
1081 |             var top = this.sprTile[i];
1082 |             if ((top & 1) !== 0) {
1083 |               top = this.sprTile[i] - 1 + 256;
1084 |             }
1085 | 
1086 |             var srcy1 = 0;
1087 |             var srcy2 = 8;
1088 | 
1089 |             if (this.sprY[i] < startscan) {
1090 |               srcy1 = startscan - this.sprY[i] - 1;
1091 |             }
1092 | 
1093 |             if (this.sprY[i] + 8 > startscan + scancount) {
1094 |               srcy2 = startscan + scancount - this.sprY[i];
1095 |             }
1096 | 
1097 |             this.ptTile[top + (this.vertFlip[i] ? 1 : 0)].render(
1098 |               this.buffer,
1099 |               0,
1100 |               srcy1,
1101 |               8,
1102 |               srcy2,
1103 |               this.sprX[i],
1104 |               this.sprY[i] + 1,
1105 |               this.sprCol[i],
1106 |               this.sprPalette,
1107 |               this.horiFlip[i],
1108 |               this.vertFlip[i],
1109 |               i,
1110 |               this.pixrendered
1111 |             );
1112 | 
1113 |             srcy1 = 0;
1114 |             srcy2 = 8;
1115 | 
1116 |             if (this.sprY[i] + 8 < startscan) {
1117 |               srcy1 = startscan - (this.sprY[i] + 8 + 1);
1118 |             }
1119 | 
1120 |             if (this.sprY[i] + 16 > startscan + scancount) {
1121 |               srcy2 = startscan + scancount - (this.sprY[i] + 8);
1122 |             }
1123 | 
1124 |             this.ptTile[top + (this.vertFlip[i] ? 0 : 1)].render(
1125 |               this.buffer,
1126 |               0,
1127 |               srcy1,
1128 |               8,
1129 |               srcy2,
1130 |               this.sprX[i],
1131 |               this.sprY[i] + 1 + 8,
1132 |               this.sprCol[i],
1133 |               this.sprPalette,
1134 |               this.horiFlip[i],
1135 |               this.vertFlip[i],
1136 |               i,
1137 |               this.pixrendered
1138 |             );
1139 |           }
1140 |         }
1141 |       }
1142 |     }
1143 |   },
1144 | 
1145 |   checkSprite0: function (scan) {
1146 |     this.spr0HitX = -1;
1147 |     this.spr0HitY = -1;
1148 | 
1149 |     var toffset;
1150 |     var tIndexAdd = this.f_spPatternTable === 0 ? 0 : 256;
1151 |     var x, y, t, i;
1152 |     var bufferIndex;
1153 | 
1154 |     x = this.sprX[0];
1155 |     y = this.sprY[0] + 1;
1156 | 
1157 |     if (this.f_spriteSize === 0) {
1158 |       // 8x8 sprites.
1159 | 
1160 |       // Check range:
1161 |       if (y <= scan && y + 8 > scan && x >= -7 && x < 256) {
1162 |         // Sprite is in range.
1163 |         // Draw scanline:
1164 |         t = this.ptTile[this.sprTile[0] + tIndexAdd];
1165 | 
1166 |         if (this.vertFlip[0]) {
1167 |           toffset = 7 - (scan - y);
1168 |         } else {
1169 |           toffset = scan - y;
1170 |         }
1171 |         toffset *= 8;
1172 | 
1173 |         bufferIndex = scan * 256 + x;
1174 |         if (this.horiFlip[0]) {
1175 |           for (i = 7; i >= 0; i--) {
1176 |             if (x >= 0 && x < 256) {
1177 |               if (
1178 |                 bufferIndex >= 0 &&
1179 |                 bufferIndex < 61440 &&
1180 |                 this.pixrendered[bufferIndex] !== 0
1181 |               ) {
1182 |                 if (t.pix[toffset + i] !== 0) {
1183 |                   this.spr0HitX = bufferIndex % 256;
1184 |                   this.spr0HitY = scan;
1185 |                   return true;
1186 |                 }
1187 |               }
1188 |             }
1189 |             x++;
1190 |             bufferIndex++;
1191 |           }
1192 |         } else {
1193 |           for (i = 0; i < 8; i++) {
1194 |             if (x >= 0 && x < 256) {
1195 |               if (
1196 |                 bufferIndex >= 0 &&
1197 |                 bufferIndex < 61440 &&
1198 |                 this.pixrendered[bufferIndex] !== 0
1199 |               ) {
1200 |                 if (t.pix[toffset + i] !== 0) {
1201 |                   this.spr0HitX = bufferIndex % 256;
1202 |                   this.spr0HitY = scan;
1203 |                   return true;
1204 |                 }
1205 |               }
1206 |             }
1207 |             x++;
1208 |             bufferIndex++;
1209 |           }
1210 |         }
1211 |       }
1212 |     } else {
1213 |       // 8x16 sprites:
1214 | 
1215 |       // Check range:
1216 |       if (y <= scan && y + 16 > scan && x >= -7 && x < 256) {
1217 |         // Sprite is in range.
1218 |         // Draw scanline:
1219 | 
1220 |         if (this.vertFlip[0]) {
1221 |           toffset = 15 - (scan - y);
1222 |         } else {
1223 |           toffset = scan - y;
1224 |         }
1225 | 
1226 |         if (toffset < 8) {
1227 |           // first half of sprite.
1228 |           t = this.ptTile[
1229 |             this.sprTile[0] +
1230 |               (this.vertFlip[0] ? 1 : 0) +
1231 |               ((this.sprTile[0] & 1) !== 0 ? 255 : 0)
1232 |           ];
1233 |         } else {
1234 |           // second half of sprite.
1235 |           t = this.ptTile[
1236 |             this.sprTile[0] +
1237 |               (this.vertFlip[0] ? 0 : 1) +
1238 |               ((this.sprTile[0] & 1) !== 0 ? 255 : 0)
1239 |           ];
1240 |           if (this.vertFlip[0]) {
1241 |             toffset = 15 - toffset;
1242 |           } else {
1243 |             toffset -= 8;
1244 |           }
1245 |         }
1246 |         toffset *= 8;
1247 | 
1248 |         bufferIndex = scan * 256 + x;
1249 |         if (this.horiFlip[0]) {
1250 |           for (i = 7; i >= 0; i--) {
1251 |             if (x >= 0 && x < 256) {
1252 |               if (
1253 |                 bufferIndex >= 0 &&
1254 |                 bufferIndex < 61440 &&
1255 |                 this.pixrendered[bufferIndex] !== 0
1256 |               ) {
1257 |                 if (t.pix[toffset + i] !== 0) {
1258 |                   this.spr0HitX = bufferIndex % 256;
1259 |                   this.spr0HitY = scan;
1260 |                   return true;
1261 |                 }
1262 |               }
1263 |             }
1264 |             x++;
1265 |             bufferIndex++;
1266 |           }
1267 |         } else {
1268 |           for (i = 0; i < 8; i++) {
1269 |             if (x >= 0 && x < 256) {
1270 |               if (
1271 |                 bufferIndex >= 0 &&
1272 |                 bufferIndex < 61440 &&
1273 |                 this.pixrendered[bufferIndex] !== 0
1274 |               ) {
1275 |                 if (t.pix[toffset + i] !== 0) {
1276 |                   this.spr0HitX = bufferIndex % 256;
1277 |                   this.spr0HitY = scan;
1278 |                   return true;
1279 |                 }
1280 |               }
1281 |             }
1282 |             x++;
1283 |             bufferIndex++;
1284 |           }
1285 |         }
1286 |       }
1287 |     }
1288 | 
1289 |     return false;
1290 |   },
1291 | 
1292 |   // This will write to PPU memory, and
1293 |   // update internally buffered data
1294 |   // appropriately.
1295 |   writeMem: function (address, value) {
1296 |     this.vramMem[address] = value;
1297 | 
1298 |     // Update internally buffered data:
1299 |     if (address < 0x2000) {
1300 |       this.vramMem[address] = value;
1301 |       this.patternWrite(address, value);
1302 |     } else if (address >= 0x2000 && address < 0x23c0) {
1303 |       this.nameTableWrite(this.ntable1[0], address - 0x2000, value);
1304 |     } else if (address >= 0x23c0 && address < 0x2400) {
1305 |       this.attribTableWrite(this.ntable1[0], address - 0x23c0, value);
1306 |     } else if (address >= 0x2400 && address < 0x27c0) {
1307 |       this.nameTableWrite(this.ntable1[1], address - 0x2400, value);
1308 |     } else if (address >= 0x27c0 && address < 0x2800) {
1309 |       this.attribTableWrite(this.ntable1[1], address - 0x27c0, value);
1310 |     } else if (address >= 0x2800 && address < 0x2bc0) {
1311 |       this.nameTableWrite(this.ntable1[2], address - 0x2800, value);
1312 |     } else if (address >= 0x2bc0 && address < 0x2c00) {
1313 |       this.attribTableWrite(this.ntable1[2], address - 0x2bc0, value);
1314 |     } else if (address >= 0x2c00 && address < 0x2fc0) {
1315 |       this.nameTableWrite(this.ntable1[3], address - 0x2c00, value);
1316 |     } else if (address >= 0x2fc0 && address < 0x3000) {
1317 |       this.attribTableWrite(this.ntable1[3], address - 0x2fc0, value);
1318 |     } else if (address >= 0x3f00 && address < 0x3f20) {
1319 |       this.updatePalettes();
1320 |     }
1321 |   },
1322 | 
1323 |   // Reads data from $3f00 to $f20
1324 |   // into the two buffered palettes.
1325 |   updatePalettes: function () {
1326 |     var i;
1327 | 
1328 |     for (i = 0; i < 16; i++) {
1329 |       if (this.f_dispType === 0) {
1330 |         this.imgPalette[i] = this.palTable.getEntry(
1331 |           this.vramMem[0x3f00 + i] & 63
1332 |         );
1333 |       } else {
1334 |         this.imgPalette[i] = this.palTable.getEntry(
1335 |           this.vramMem[0x3f00 + i] & 32
1336 |         );
1337 |       }
1338 |     }
1339 |     for (i = 0; i < 16; i++) {
1340 |       if (this.f_dispType === 0) {
1341 |         this.sprPalette[i] = this.palTable.getEntry(
1342 |           this.vramMem[0x3f10 + i] & 63
1343 |         );
1344 |       } else {
1345 |         this.sprPalette[i] = this.palTable.getEntry(
1346 |           this.vramMem[0x3f10 + i] & 32
1347 |         );
1348 |       }
1349 |     }
1350 |   },
1351 | 
1352 |   // Updates the internal pattern
1353 |   // table buffers with this new byte.
1354 |   // In vNES, there is a version of this with 4 arguments which isn't used.
1355 |   patternWrite: function (address, value) {
1356 |     var tileIndex = Math.floor(address / 16);
1357 |     var leftOver = address % 16;
1358 |     if (leftOver < 8) {
1359 |       this.ptTile[tileIndex].setScanline(
1360 |         leftOver,
1361 |         value,
1362 |         this.vramMem[address + 8]
1363 |       );
1364 |     } else {
1365 |       this.ptTile[tileIndex].setScanline(
1366 |         leftOver - 8,
1367 |         this.vramMem[address - 8],
1368 |         value
1369 |       );
1370 |     }
1371 |   },
1372 | 
1373 |   // Updates the internal name table buffers
1374 |   // with this new byte.
1375 |   nameTableWrite: function (index, address, value) {
1376 |     this.nameTable[index].tile[address] = value;
1377 | 
1378 |     // Update Sprite #0 hit:
1379 |     //updateSpr0Hit();
1380 |     this.checkSprite0(this.scanline - 20);
1381 |   },
1382 | 
1383 |   // Updates the internal pattern
1384 |   // table buffers with this new attribute
1385 |   // table byte.
1386 |   attribTableWrite: function (index, address, value) {
1387 |     this.nameTable[index].writeAttrib(address, value);
1388 |   },
1389 | 
1390 |   // Updates the internally buffered sprite
1391 |   // data with this new byte of info.
1392 |   spriteRamWriteUpdate: function (address, value) {
1393 |     var tIndex = Math.floor(address / 4);
1394 | 
1395 |     if (tIndex === 0) {
1396 |       //updateSpr0Hit();
1397 |       this.checkSprite0(this.scanline - 20);
1398 |     }
1399 | 
1400 |     if (address % 4 === 0) {
1401 |       // Y coordinate
1402 |       this.sprY[tIndex] = value;
1403 |     } else if (address % 4 === 1) {
1404 |       // Tile index
1405 |       this.sprTile[tIndex] = value;
1406 |     } else if (address % 4 === 2) {
1407 |       // Attributes
1408 |       this.vertFlip[tIndex] = (value & 0x80) !== 0;
1409 |       this.horiFlip[tIndex] = (value & 0x40) !== 0;
1410 |       this.bgPriority[tIndex] = (value & 0x20) !== 0;
1411 |       this.sprCol[tIndex] = (value & 3) << 2;
1412 |     } else if (address % 4 === 3) {
1413 |       // X coordinate
1414 |       this.sprX[tIndex] = value;
1415 |     }
1416 |   },
1417 | 
1418 |   doNMI: function () {
1419 |     // Set VBlank flag:
1420 |     this.setStatusFlag(this.STATUS_VBLANK, true);
1421 |     //nes.getCpu().doNonMaskableInterrupt();
1422 |     this.nes.cpu.requestIrq(this.nes.cpu.IRQ_NMI);
1423 |   },
1424 | 
1425 |   isPixelWhite: function (x, y) {
1426 |     this.triggerRendering();
1427 |     return this.nes.ppu.buffer[(y << 8) + x] === 0xffffff;
1428 |   },
1429 | 
1430 |   JSON_PROPERTIES: [
1431 |     // Memory
1432 |     "vramMem",
1433 |     "spriteMem",
1434 |     // Counters
1435 |     "cntFV",
1436 |     "cntV",
1437 |     "cntH",
1438 |     "cntVT",
1439 |     "cntHT",
1440 |     // Registers
1441 |     "regFV",
1442 |     "regV",
1443 |     "regH",
1444 |     "regVT",
1445 |     "regHT",
1446 |     "regFH",
1447 |     "regS",
1448 |     // VRAM addr
1449 |     "vramAddress",
1450 |     "vramTmpAddress",
1451 |     // Control/Status registers
1452 |     "f_nmiOnVblank",
1453 |     "f_spriteSize",
1454 |     "f_bgPatternTable",
1455 |     "f_spPatternTable",
1456 |     "f_addrInc",
1457 |     "f_nTblAddress",
1458 |     "f_color",
1459 |     "f_spVisibility",
1460 |     "f_bgVisibility",
1461 |     "f_spClipping",
1462 |     "f_bgClipping",
1463 |     "f_dispType",
1464 |     // VRAM I/O
1465 |     "vramBufferedReadValue",
1466 |     "firstWrite",
1467 |     // Mirroring
1468 |     "currentMirroring",
1469 |     "vramMirrorTable",
1470 |     "ntable1",
1471 |     // SPR-RAM I/O
1472 |     "sramAddress",
1473 |     // Sprites. Most sprite data is rebuilt from spriteMem
1474 |     "hitSpr0",
1475 |     // Palettes
1476 |     "sprPalette",
1477 |     "imgPalette",
1478 |     // Rendering progression
1479 |     "curX",
1480 |     "scanline",
1481 |     "lastRenderedScanline",
1482 |     "curNt",
1483 |     "scantile",
1484 |     // Used during rendering
1485 |     "attrib",
1486 |     "buffer",
1487 |     "bgbuffer",
1488 |     "pixrendered",
1489 |     // Misc
1490 |     "requestEndFrame",
1491 |     "nmiOk",
1492 |     "dummyCycleToggle",
1493 |     "nmiCounter",
1494 |     "validTileData",
1495 |     "scanlineAlreadyRendered",
1496 |   ],
1497 | 
1498 |   toJSON: function () {
1499 |     var i;
1500 |     var state = utils.toJSON(this);
1501 | 
1502 |     state.nameTable = [];
1503 |     for (i = 0; i < this.nameTable.length; i++) {
1504 |       state.nameTable[i] = this.nameTable[i].toJSON();
1505 |     }
1506 | 
1507 |     state.ptTile = [];
1508 |     for (i = 0; i < this.ptTile.length; i++) {
1509 |       state.ptTile[i] = this.ptTile[i].toJSON();
1510 |     }
1511 | 
1512 |     return state;
1513 |   },
1514 | 
1515 |   fromJSON: function (state) {
1516 |     var i;
1517 | 
1518 |     utils.fromJSON(this, state);
1519 | 
1520 |     for (i = 0; i < this.nameTable.length; i++) {
1521 |       this.nameTable[i].fromJSON(state.nameTable[i]);
1522 |     }
1523 | 
1524 |     for (i = 0; i < this.ptTile.length; i++) {
1525 |       this.ptTile[i].fromJSON(state.ptTile[i]);
1526 |     }
1527 | 
1528 |     // Sprite data:
1529 |     for (i = 0; i < this.spriteMem.length; i++) {
1530 |       this.spriteRamWriteUpdate(i, this.spriteMem[i]);
1531 |     }
1532 |   },
1533 | };
1534 | 
1535 | var NameTable = function (width, height, name) {
1536 |   this.width = width;
1537 |   this.height = height;
1538 |   this.name = name;
1539 | 
1540 |   this.tile = new Array(width * height);
1541 |   this.attrib = new Array(width * height);
1542 |   for (var i = 0; i < width * height; i++) {
1543 |     this.tile[i] = 0;
1544 |     this.attrib[i] = 0;
1545 |   }
1546 | };
1547 | 
1548 | NameTable.prototype = {
1549 |   getTileIndex: function (x, y) {
1550 |     return this.tile[y * this.width + x];
1551 |   },
1552 | 
1553 |   getAttrib: function (x, y) {
1554 |     return this.attrib[y * this.width + x];
1555 |   },
1556 | 
1557 |   writeAttrib: function (index, value) {
1558 |     var basex = (index % 8) * 4;
1559 |     var basey = Math.floor(index / 8) * 4;
1560 |     var add;
1561 |     var tx, ty;
1562 |     var attindex;
1563 | 
1564 |     for (var sqy = 0; sqy < 2; sqy++) {
1565 |       for (var sqx = 0; sqx < 2; sqx++) {
1566 |         add = (value >> (2 * (sqy * 2 + sqx))) & 3;
1567 |         for (var y = 0; y < 2; y++) {
1568 |           for (var x = 0; x < 2; x++) {
1569 |             tx = basex + sqx * 2 + x;
1570 |             ty = basey + sqy * 2 + y;
1571 |             attindex = ty * this.width + tx;
1572 |             this.attrib[attindex] = (add << 2) & 12;
1573 |           }
1574 |         }
1575 |       }
1576 |     }
1577 |   },
1578 | 
1579 |   toJSON: function () {
1580 |     return {
1581 |       tile: this.tile,
1582 |       attrib: this.attrib,
1583 |     };
1584 |   },
1585 | 
1586 |   fromJSON: function (s) {
1587 |     this.tile = s.tile;
1588 |     this.attrib = s.attrib;
1589 |   },
1590 | };
1591 | 
1592 | var PaletteTable = function () {
1593 |   this.curTable = new Array(64);
1594 |   this.emphTable = new Array(8);
1595 |   this.currentEmph = -1;
1596 | };
1597 | 
1598 | PaletteTable.prototype = {
1599 |   reset: function () {
1600 |     this.setEmphasis(0);
1601 |   },
1602 | 
1603 |   loadNTSCPalette: function () {
1604 |     // prettier-ignore
1605 |     this.curTable = [0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000, 0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000];
1606 |     this.makeTables();
1607 |     this.setEmphasis(0);
1608 |   },
1609 | 
1610 |   loadPALPalette: function () {
1611 |     // prettier-ignore
1612 |     this.curTable = [0x525252, 0xB40000, 0xA00000, 0xB1003D, 0x740069, 0x00005B, 0x00005F, 0x001840, 0x002F10, 0x084A08, 0x006700, 0x124200, 0x6D2800, 0x000000, 0x000000, 0x000000, 0xC4D5E7, 0xFF4000, 0xDC0E22, 0xFF476B, 0xD7009F, 0x680AD7, 0x0019BC, 0x0054B1, 0x006A5B, 0x008C03, 0x00AB00, 0x2C8800, 0xA47200, 0x000000, 0x000000, 0x000000, 0xF8F8F8, 0xFFAB3C, 0xFF7981, 0xFF5BC5, 0xFF48F2, 0xDF49FF, 0x476DFF, 0x00B4F7, 0x00E0FF, 0x00E375, 0x03F42B, 0x78B82E, 0xE5E218, 0x787878, 0x000000, 0x000000, 0xFFFFFF, 0xFFF2BE, 0xF8B8B8, 0xF8B8D8, 0xFFB6FF, 0xFFC3FF, 0xC7D1FF, 0x9ADAFF, 0x88EDF8, 0x83FFDD, 0xB8F8B8, 0xF5F8AC, 0xFFFFB0, 0xF8D8F8, 0x000000, 0x000000];
1613 |     this.makeTables();
1614 |     this.setEmphasis(0);
1615 |   },
1616 | 
1617 |   makeTables: function () {
1618 |     var r, g, b, col, i, rFactor, gFactor, bFactor;
1619 | 
1620 |     // Calculate a table for each possible emphasis setting:
1621 |     for (var emph = 0; emph < 8; emph++) {
1622 |       // Determine color component factors:
1623 |       rFactor = 1.0;
1624 |       gFactor = 1.0;
1625 |       bFactor = 1.0;
1626 | 
1627 |       if ((emph & 1) !== 0) {
1628 |         rFactor = 0.75;
1629 |         bFactor = 0.75;
1630 |       }
1631 |       if ((emph & 2) !== 0) {
1632 |         rFactor = 0.75;
1633 |         gFactor = 0.75;
1634 |       }
1635 |       if ((emph & 4) !== 0) {
1636 |         gFactor = 0.75;
1637 |         bFactor = 0.75;
1638 |       }
1639 | 
1640 |       this.emphTable[emph] = new Array(64);
1641 | 
1642 |       // Calculate table:
1643 |       for (i = 0; i < 64; i++) {
1644 |         col = this.curTable[i];
1645 |         r = Math.floor(this.getRed(col) * rFactor);
1646 |         g = Math.floor(this.getGreen(col) * gFactor);
1647 |         b = Math.floor(this.getBlue(col) * bFactor);
1648 |         this.emphTable[emph][i] = this.getRgb(r, g, b);
1649 |       }
1650 |     }
1651 |   },
1652 | 
1653 |   setEmphasis: function (emph) {
1654 |     if (emph !== this.currentEmph) {
1655 |       this.currentEmph = emph;
1656 |       for (var i = 0; i < 64; i++) {
1657 |         this.curTable[i] = this.emphTable[emph][i];
1658 |       }
1659 |     }
1660 |   },
1661 | 
1662 |   getEntry: function (yiq) {
1663 |     return this.curTable[yiq];
1664 |   },
1665 | 
1666 |   getRed: function (rgb) {
1667 |     return (rgb >> 16) & 0xff;
1668 |   },
1669 | 
1670 |   getGreen: function (rgb) {
1671 |     return (rgb >> 8) & 0xff;
1672 |   },
1673 | 
1674 |   getBlue: function (rgb) {
1675 |     return rgb & 0xff;
1676 |   },
1677 | 
1678 |   getRgb: function (r, g, b) {
1679 |     return (r << 16) | (g << 8) | b;
1680 |   },
1681 | 
1682 |   loadDefaultPalette: function () {
1683 |     this.curTable[0] = this.getRgb(117, 117, 117);
1684 |     this.curTable[1] = this.getRgb(39, 27, 143);
1685 |     this.curTable[2] = this.getRgb(0, 0, 171);
1686 |     this.curTable[3] = this.getRgb(71, 0, 159);
1687 |     this.curTable[4] = this.getRgb(143, 0, 119);
1688 |     this.curTable[5] = this.getRgb(171, 0, 19);
1689 |     this.curTable[6] = this.getRgb(167, 0, 0);
1690 |     this.curTable[7] = this.getRgb(127, 11, 0);
1691 |     this.curTable[8] = this.getRgb(67, 47, 0);
1692 |     this.curTable[9] = this.getRgb(0, 71, 0);
1693 |     this.curTable[10] = this.getRgb(0, 81, 0);
1694 |     this.curTable[11] = this.getRgb(0, 63, 23);
1695 |     this.curTable[12] = this.getRgb(27, 63, 95);
1696 |     this.curTable[13] = this.getRgb(0, 0, 0);
1697 |     this.curTable[14] = this.getRgb(0, 0, 0);
1698 |     this.curTable[15] = this.getRgb(0, 0, 0);
1699 |     this.curTable[16] = this.getRgb(188, 188, 188);
1700 |     this.curTable[17] = this.getRgb(0, 115, 239);
1701 |     this.curTable[18] = this.getRgb(35, 59, 239);
1702 |     this.curTable[19] = this.getRgb(131, 0, 243);
1703 |     this.curTable[20] = this.getRgb(191, 0, 191);
1704 |     this.curTable[21] = this.getRgb(231, 0, 91);
1705 |     this.curTable[22] = this.getRgb(219, 43, 0);
1706 |     this.curTable[23] = this.getRgb(203, 79, 15);
1707 |     this.curTable[24] = this.getRgb(139, 115, 0);
1708 |     this.curTable[25] = this.getRgb(0, 151, 0);
1709 |     this.curTable[26] = this.getRgb(0, 171, 0);
1710 |     this.curTable[27] = this.getRgb(0, 147, 59);
1711 |     this.curTable[28] = this.getRgb(0, 131, 139);
1712 |     this.curTable[29] = this.getRgb(0, 0, 0);
1713 |     this.curTable[30] = this.getRgb(0, 0, 0);
1714 |     this.curTable[31] = this.getRgb(0, 0, 0);
1715 |     this.curTable[32] = this.getRgb(255, 255, 255);
1716 |     this.curTable[33] = this.getRgb(63, 191, 255);
1717 |     this.curTable[34] = this.getRgb(95, 151, 255);
1718 |     this.curTable[35] = this.getRgb(167, 139, 253);
1719 |     this.curTable[36] = this.getRgb(247, 123, 255);
1720 |     this.curTable[37] = this.getRgb(255, 119, 183);
1721 |     this.curTable[38] = this.getRgb(255, 119, 99);
1722 |     this.curTable[39] = this.getRgb(255, 155, 59);
1723 |     this.curTable[40] = this.getRgb(243, 191, 63);
1724 |     this.curTable[41] = this.getRgb(131, 211, 19);
1725 |     this.curTable[42] = this.getRgb(79, 223, 75);
1726 |     this.curTable[43] = this.getRgb(88, 248, 152);
1727 |     this.curTable[44] = this.getRgb(0, 235, 219);
1728 |     this.curTable[45] = this.getRgb(0, 0, 0);
1729 |     this.curTable[46] = this.getRgb(0, 0, 0);
1730 |     this.curTable[47] = this.getRgb(0, 0, 0);
1731 |     this.curTable[48] = this.getRgb(255, 255, 255);
1732 |     this.curTable[49] = this.getRgb(171, 231, 255);
1733 |     this.curTable[50] = this.getRgb(199, 215, 255);
1734 |     this.curTable[51] = this.getRgb(215, 203, 255);
1735 |     this.curTable[52] = this.getRgb(255, 199, 255);
1736 |     this.curTable[53] = this.getRgb(255, 199, 219);
1737 |     this.curTable[54] = this.getRgb(255, 191, 179);
1738 |     this.curTable[55] = this.getRgb(255, 219, 171);
1739 |     this.curTable[56] = this.getRgb(255, 231, 163);
1740 |     this.curTable[57] = this.getRgb(227, 255, 163);
1741 |     this.curTable[58] = this.getRgb(171, 243, 191);
1742 |     this.curTable[59] = this.getRgb(179, 255, 207);
1743 |     this.curTable[60] = this.getRgb(159, 255, 243);
1744 |     this.curTable[61] = this.getRgb(0, 0, 0);
1745 |     this.curTable[62] = this.getRgb(0, 0, 0);
1746 |     this.curTable[63] = this.getRgb(0, 0, 0);
1747 | 
1748 |     this.makeTables();
1749 |     this.setEmphasis(0);
1750 |   },
1751 | };
1752 | 
1753 | module.exports = PPU;
1754 | 


--------------------------------------------------------------------------------
/src/rom.js:
--------------------------------------------------------------------------------
  1 | var Mappers = require("./mappers");
  2 | var Tile = require("./tile");
  3 | 
  4 | var ROM = function (nes) {
  5 |   this.nes = nes;
  6 | 
  7 |   this.mapperName = new Array(92);
  8 | 
  9 |   for (var i = 0; i < 92; i++) {
 10 |     this.mapperName[i] = "Unknown Mapper";
 11 |   }
 12 |   this.mapperName[0] = "Direct Access";
 13 |   this.mapperName[1] = "Nintendo MMC1";
 14 |   this.mapperName[2] = "UNROM";
 15 |   this.mapperName[3] = "CNROM";
 16 |   this.mapperName[4] = "Nintendo MMC3";
 17 |   this.mapperName[5] = "Nintendo MMC5";
 18 |   this.mapperName[6] = "FFE F4xxx";
 19 |   this.mapperName[7] = "AOROM";
 20 |   this.mapperName[8] = "FFE F3xxx";
 21 |   this.mapperName[9] = "Nintendo MMC2";
 22 |   this.mapperName[10] = "Nintendo MMC4";
 23 |   this.mapperName[11] = "Color Dreams Chip";
 24 |   this.mapperName[12] = "FFE F6xxx";
 25 |   this.mapperName[15] = "100-in-1 switch";
 26 |   this.mapperName[16] = "Bandai chip";
 27 |   this.mapperName[17] = "FFE F8xxx";
 28 |   this.mapperName[18] = "Jaleco SS8806 chip";
 29 |   this.mapperName[19] = "Namcot 106 chip";
 30 |   this.mapperName[20] = "Famicom Disk System";
 31 |   this.mapperName[21] = "Konami VRC4a";
 32 |   this.mapperName[22] = "Konami VRC2a";
 33 |   this.mapperName[23] = "Konami VRC2a";
 34 |   this.mapperName[24] = "Konami VRC6";
 35 |   this.mapperName[25] = "Konami VRC4b";
 36 |   this.mapperName[32] = "Irem G-101 chip";
 37 |   this.mapperName[33] = "Taito TC0190/TC0350";
 38 |   this.mapperName[34] = "32kB ROM switch";
 39 | 
 40 |   this.mapperName[64] = "Tengen RAMBO-1 chip";
 41 |   this.mapperName[65] = "Irem H-3001 chip";
 42 |   this.mapperName[66] = "GNROM switch";
 43 |   this.mapperName[67] = "SunSoft3 chip";
 44 |   this.mapperName[68] = "SunSoft4 chip";
 45 |   this.mapperName[69] = "SunSoft5 FME-7 chip";
 46 |   this.mapperName[71] = "Camerica chip";
 47 |   this.mapperName[78] = "Irem 74HC161/32-based";
 48 |   this.mapperName[91] = "Pirate HK-SF3 chip";
 49 | };
 50 | 
 51 | ROM.prototype = {
 52 |   // Mirroring types:
 53 |   VERTICAL_MIRRORING: 0,
 54 |   HORIZONTAL_MIRRORING: 1,
 55 |   FOURSCREEN_MIRRORING: 2,
 56 |   SINGLESCREEN_MIRRORING: 3,
 57 |   SINGLESCREEN_MIRRORING2: 4,
 58 |   SINGLESCREEN_MIRRORING3: 5,
 59 |   SINGLESCREEN_MIRRORING4: 6,
 60 |   CHRROM_MIRRORING: 7,
 61 | 
 62 |   header: null,
 63 |   rom: null,
 64 |   vrom: null,
 65 |   vromTile: null,
 66 | 
 67 |   romCount: null,
 68 |   vromCount: null,
 69 |   mirroring: null,
 70 |   batteryRam: null,
 71 |   trainer: null,
 72 |   fourScreen: null,
 73 |   mapperType: null,
 74 |   valid: false,
 75 | 
 76 |   load: function (data) {
 77 |     var i, j, v;
 78 | 
 79 |     if (data.indexOf("NES\x1a") === -1) {
 80 |       throw new Error("Not a valid NES ROM.");
 81 |     }
 82 |     this.header = new Array(16);
 83 |     for (i = 0; i < 16; i++) {
 84 |       this.header[i] = data.charCodeAt(i) & 0xff;
 85 |     }
 86 |     this.romCount = this.header[4];
 87 |     this.vromCount = this.header[5] * 2; // Get the number of 4kB banks, not 8kB
 88 |     this.mirroring = (this.header[6] & 1) !== 0 ? 1 : 0;
 89 |     this.batteryRam = (this.header[6] & 2) !== 0;
 90 |     this.trainer = (this.header[6] & 4) !== 0;
 91 |     this.fourScreen = (this.header[6] & 8) !== 0;
 92 |     this.mapperType = (this.header[6] >> 4) | (this.header[7] & 0xf0);
 93 |     /* TODO
 94 |         if (this.batteryRam)
 95 |             this.loadBatteryRam();*/
 96 |     // Check whether byte 8-15 are zero's:
 97 |     var foundError = false;
 98 |     for (i = 8; i < 16; i++) {
 99 |       if (this.header[i] !== 0) {
100 |         foundError = true;
101 |         break;
102 |       }
103 |     }
104 |     if (foundError) {
105 |       this.mapperType &= 0xf; // Ignore byte 7
106 |     }
107 |     // Load PRG-ROM banks:
108 |     this.rom = new Array(this.romCount);
109 |     var offset = 16;
110 |     for (i = 0; i < this.romCount; i++) {
111 |       this.rom[i] = new Array(16384);
112 |       for (j = 0; j < 16384; j++) {
113 |         if (offset + j >= data.length) {
114 |           break;
115 |         }
116 |         this.rom[i][j] = data.charCodeAt(offset + j) & 0xff;
117 |       }
118 |       offset += 16384;
119 |     }
120 |     // Load CHR-ROM banks:
121 |     this.vrom = new Array(this.vromCount);
122 |     for (i = 0; i < this.vromCount; i++) {
123 |       this.vrom[i] = new Array(4096);
124 |       for (j = 0; j < 4096; j++) {
125 |         if (offset + j >= data.length) {
126 |           break;
127 |         }
128 |         this.vrom[i][j] = data.charCodeAt(offset + j) & 0xff;
129 |       }
130 |       offset += 4096;
131 |     }
132 | 
133 |     // Create VROM tiles:
134 |     this.vromTile = new Array(this.vromCount);
135 |     for (i = 0; i < this.vromCount; i++) {
136 |       this.vromTile[i] = new Array(256);
137 |       for (j = 0; j < 256; j++) {
138 |         this.vromTile[i][j] = new Tile();
139 |       }
140 |     }
141 | 
142 |     // Convert CHR-ROM banks to tiles:
143 |     var tileIndex;
144 |     var leftOver;
145 |     for (v = 0; v < this.vromCount; v++) {
146 |       for (i = 0; i < 4096; i++) {
147 |         tileIndex = i >> 4;
148 |         leftOver = i % 16;
149 |         if (leftOver < 8) {
150 |           this.vromTile[v][tileIndex].setScanline(
151 |             leftOver,
152 |             this.vrom[v][i],
153 |             this.vrom[v][i + 8]
154 |           );
155 |         } else {
156 |           this.vromTile[v][tileIndex].setScanline(
157 |             leftOver - 8,
158 |             this.vrom[v][i - 8],
159 |             this.vrom[v][i]
160 |           );
161 |         }
162 |       }
163 |     }
164 | 
165 |     this.valid = true;
166 |   },
167 | 
168 |   getMirroringType: function () {
169 |     if (this.fourScreen) {
170 |       return this.FOURSCREEN_MIRRORING;
171 |     }
172 |     if (this.mirroring === 0) {
173 |       return this.HORIZONTAL_MIRRORING;
174 |     }
175 |     return this.VERTICAL_MIRRORING;
176 |   },
177 | 
178 |   getMapperName: function () {
179 |     if (this.mapperType >= 0 && this.mapperType < this.mapperName.length) {
180 |       return this.mapperName[this.mapperType];
181 |     }
182 |     return "Unknown Mapper, " + this.mapperType;
183 |   },
184 | 
185 |   mapperSupported: function () {
186 |     return typeof Mappers[this.mapperType] !== "undefined";
187 |   },
188 | 
189 |   createMapper: function () {
190 |     if (this.mapperSupported()) {
191 |       return new Mappers[this.mapperType](this.nes);
192 |     } else {
193 |       throw new Error(
194 |         "This ROM uses a mapper not supported by JSNES: " +
195 |           this.getMapperName() +
196 |           "(" +
197 |           this.mapperType +
198 |           ")"
199 |       );
200 |     }
201 |   },
202 | };
203 | 
204 | module.exports = ROM;
205 | 


--------------------------------------------------------------------------------
/src/tile.js:
--------------------------------------------------------------------------------
  1 | var Tile = function () {
  2 |   // Tile data:
  3 |   this.pix = new Array(64);
  4 | 
  5 |   this.fbIndex = null;
  6 |   this.tIndex = null;
  7 |   this.x = null;
  8 |   this.y = null;
  9 |   this.w = null;
 10 |   this.h = null;
 11 |   this.incX = null;
 12 |   this.incY = null;
 13 |   this.palIndex = null;
 14 |   this.tpri = null;
 15 |   this.c = null;
 16 |   this.initialized = false;
 17 |   this.opaque = new Array(8);
 18 | };
 19 | 
 20 | Tile.prototype = {
 21 |   setBuffer: function (scanline) {
 22 |     for (this.y = 0; this.y < 8; this.y++) {
 23 |       this.setScanline(this.y, scanline[this.y], scanline[this.y + 8]);
 24 |     }
 25 |   },
 26 | 
 27 |   setScanline: function (sline, b1, b2) {
 28 |     this.initialized = true;
 29 |     this.tIndex = sline << 3;
 30 |     for (this.x = 0; this.x < 8; this.x++) {
 31 |       this.pix[this.tIndex + this.x] =
 32 |         ((b1 >> (7 - this.x)) & 1) + (((b2 >> (7 - this.x)) & 1) << 1);
 33 |       if (this.pix[this.tIndex + this.x] === 0) {
 34 |         this.opaque[sline] = false;
 35 |       }
 36 |     }
 37 |   },
 38 | 
 39 |   render: function (
 40 |     buffer,
 41 |     srcx1,
 42 |     srcy1,
 43 |     srcx2,
 44 |     srcy2,
 45 |     dx,
 46 |     dy,
 47 |     palAdd,
 48 |     palette,
 49 |     flipHorizontal,
 50 |     flipVertical,
 51 |     pri,
 52 |     priTable
 53 |   ) {
 54 |     if (dx < -7 || dx >= 256 || dy < -7 || dy >= 240) {
 55 |       return;
 56 |     }
 57 | 
 58 |     this.w = srcx2 - srcx1;
 59 |     this.h = srcy2 - srcy1;
 60 | 
 61 |     if (dx < 0) {
 62 |       srcx1 -= dx;
 63 |     }
 64 |     if (dx + srcx2 >= 256) {
 65 |       srcx2 = 256 - dx;
 66 |     }
 67 | 
 68 |     if (dy < 0) {
 69 |       srcy1 -= dy;
 70 |     }
 71 |     if (dy + srcy2 >= 240) {
 72 |       srcy2 = 240 - dy;
 73 |     }
 74 | 
 75 |     if (!flipHorizontal && !flipVertical) {
 76 |       this.fbIndex = (dy << 8) + dx;
 77 |       this.tIndex = 0;
 78 |       for (this.y = 0; this.y < 8; this.y++) {
 79 |         for (this.x = 0; this.x < 8; this.x++) {
 80 |           if (
 81 |             this.x >= srcx1 &&
 82 |             this.x < srcx2 &&
 83 |             this.y >= srcy1 &&
 84 |             this.y < srcy2
 85 |           ) {
 86 |             this.palIndex = this.pix[this.tIndex];
 87 |             this.tpri = priTable[this.fbIndex];
 88 |             if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {
 89 |               //console.log("Rendering upright tile to buffer");
 90 |               buffer[this.fbIndex] = palette[this.palIndex + palAdd];
 91 |               this.tpri = (this.tpri & 0xf00) | pri;
 92 |               priTable[this.fbIndex] = this.tpri;
 93 |             }
 94 |           }
 95 |           this.fbIndex++;
 96 |           this.tIndex++;
 97 |         }
 98 |         this.fbIndex -= 8;
 99 |         this.fbIndex += 256;
100 |       }
101 |     } else if (flipHorizontal && !flipVertical) {
102 |       this.fbIndex = (dy << 8) + dx;
103 |       this.tIndex = 7;
104 |       for (this.y = 0; this.y < 8; this.y++) {
105 |         for (this.x = 0; this.x < 8; this.x++) {
106 |           if (
107 |             this.x >= srcx1 &&
108 |             this.x < srcx2 &&
109 |             this.y >= srcy1 &&
110 |             this.y < srcy2
111 |           ) {
112 |             this.palIndex = this.pix[this.tIndex];
113 |             this.tpri = priTable[this.fbIndex];
114 |             if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {
115 |               buffer[this.fbIndex] = palette[this.palIndex + palAdd];
116 |               this.tpri = (this.tpri & 0xf00) | pri;
117 |               priTable[this.fbIndex] = this.tpri;
118 |             }
119 |           }
120 |           this.fbIndex++;
121 |           this.tIndex--;
122 |         }
123 |         this.fbIndex -= 8;
124 |         this.fbIndex += 256;
125 |         this.tIndex += 16;
126 |       }
127 |     } else if (flipVertical && !flipHorizontal) {
128 |       this.fbIndex = (dy << 8) + dx;
129 |       this.tIndex = 56;
130 |       for (this.y = 0; this.y < 8; this.y++) {
131 |         for (this.x = 0; this.x < 8; this.x++) {
132 |           if (
133 |             this.x >= srcx1 &&
134 |             this.x < srcx2 &&
135 |             this.y >= srcy1 &&
136 |             this.y < srcy2
137 |           ) {
138 |             this.palIndex = this.pix[this.tIndex];
139 |             this.tpri = priTable[this.fbIndex];
140 |             if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {
141 |               buffer[this.fbIndex] = palette[this.palIndex + palAdd];
142 |               this.tpri = (this.tpri & 0xf00) | pri;
143 |               priTable[this.fbIndex] = this.tpri;
144 |             }
145 |           }
146 |           this.fbIndex++;
147 |           this.tIndex++;
148 |         }
149 |         this.fbIndex -= 8;
150 |         this.fbIndex += 256;
151 |         this.tIndex -= 16;
152 |       }
153 |     } else {
154 |       this.fbIndex = (dy << 8) + dx;
155 |       this.tIndex = 63;
156 |       for (this.y = 0; this.y < 8; this.y++) {
157 |         for (this.x = 0; this.x < 8; this.x++) {
158 |           if (
159 |             this.x >= srcx1 &&
160 |             this.x < srcx2 &&
161 |             this.y >= srcy1 &&
162 |             this.y < srcy2
163 |           ) {
164 |             this.palIndex = this.pix[this.tIndex];
165 |             this.tpri = priTable[this.fbIndex];
166 |             if (this.palIndex !== 0 && pri <= (this.tpri & 0xff)) {
167 |               buffer[this.fbIndex] = palette[this.palIndex + palAdd];
168 |               this.tpri = (this.tpri & 0xf00) | pri;
169 |               priTable[this.fbIndex] = this.tpri;
170 |             }
171 |           }
172 |           this.fbIndex++;
173 |           this.tIndex--;
174 |         }
175 |         this.fbIndex -= 8;
176 |         this.fbIndex += 256;
177 |       }
178 |     }
179 |   },
180 | 
181 |   isTransparent: function (x, y) {
182 |     return this.pix[(y << 3) + x] === 0;
183 |   },
184 | 
185 |   toJSON: function () {
186 |     return {
187 |       opaque: this.opaque,
188 |       pix: this.pix,
189 |     };
190 |   },
191 | 
192 |   fromJSON: function (s) {
193 |     this.opaque = s.opaque;
194 |     this.pix = s.pix;
195 |   },
196 | };
197 | 
198 | module.exports = Tile;
199 | 


--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
 1 | module.exports = {
 2 |   copyArrayElements: function (src, srcPos, dest, destPos, length) {
 3 |     for (var i = 0; i < length; ++i) {
 4 |       dest[destPos + i] = src[srcPos + i];
 5 |     }
 6 |   },
 7 | 
 8 |   copyArray: function (src) {
 9 |     return src.slice(0);
10 |   },
11 | 
12 |   fromJSON: function (obj, state) {
13 |     for (var i = 0; i < obj.JSON_PROPERTIES.length; i++) {
14 |       obj[obj.JSON_PROPERTIES[i]] = state[obj.JSON_PROPERTIES[i]];
15 |     }
16 |   },
17 | 
18 |   toJSON: function (obj) {
19 |     var state = {};
20 |     for (var i = 0; i < obj.JSON_PROPERTIES.length; i++) {
21 |       state[obj.JSON_PROPERTIES[i]] = obj[obj.JSON_PROPERTIES[i]];
22 |     }
23 |     return state;
24 |   },
25 | };
26 | 


--------------------------------------------------------------------------------
/test/nes.spec.js:
--------------------------------------------------------------------------------
 1 | var assert = require("chai").assert;
 2 | var fs = require("fs");
 3 | var NES = require("../src/nes");
 4 | var sinon = require("sinon");
 5 | 
 6 | describe("NES", function() {
 7 |   it("can be initialized", function() {
 8 |     var nes = new NES();
 9 |   });
10 | 
11 |   it("loads a ROM and runs a frame", function(done) {
12 |     var onFrame = sinon.spy();
13 |     var nes = new NES({ onFrame: onFrame });
14 |     fs.readFile("roms/croom/croom.nes", function(err, data) {
15 |       if (err) return done(err);
16 |       nes.loadROM(data.toString("binary"));
17 |       nes.frame();
18 |       assert(onFrame.calledOnce);
19 |       assert.isArray(onFrame.args[0][0]);
20 |       assert.lengthOf(onFrame.args[0][0], 256 * 240);
21 |       done();
22 |     });
23 |   });
24 | 
25 |   it("generates the correct frame buffer", function(done) {
26 |     var onFrame = sinon.spy();
27 |     var nes = new NES({ onFrame: onFrame });
28 |     fs.readFile("roms/croom/croom.nes", function(err, data) {
29 |       if (err) return done(err);
30 |       nes.loadROM(data.toString("binary"));
31 |       // Check the first index of a white pixel on the first 6 frames of
32 |       // output. Croom only uses 2 colors on the initial screen which makes
33 |       // it easy to detect. Comparing full snapshots of each frame takes too
34 |       // long.
35 |       var expectedIndexes = [-1, -1, -1, 2056, 4104, 4104];
36 |       for (var i = 0; i < 6; i++) {
37 |         nes.frame();
38 |         assert.equal(onFrame.lastCall.args[0].indexOf(0xFFFFFF), expectedIndexes[i]);
39 |       }
40 |       done();
41 |     });
42 |   });
43 | 
44 |   describe("#loadROM()", function() {
45 |     it("throws an error given an invalid ROM", function() {
46 |       var nes = new NES();
47 |       assert.throws(function() {
48 |         nes.loadROM("foo");
49 |       }, "Not a valid NES ROM.");
50 |     });
51 |   });
52 | 
53 |   describe("#getFPS()", function() {
54 |     var nes = new NES();
55 |     before(function(done) {
56 |       fs.readFile("roms/croom/croom.nes", function(err, data) {
57 |         if (err) return done(err);
58 |         nes.loadROM(data.toString("binary"));
59 |         done();
60 |       });
61 |     });
62 | 
63 |     it("returns an FPS count when frames have been run", function() {
64 |       assert.isNull(nes.getFPS());
65 |       nes.frame();
66 |       nes.frame();
67 |       var fps = nes.getFPS();
68 |       assert.isNumber(fps);
69 |       assert.isAbove(fps, 0);
70 |     });
71 |   });
72 | });
73 | 


--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
 1 | var path = require("path");
 2 | const UglifyJsPlugin = require("uglifyjs-webpack-plugin");
 3 | 
 4 | module.exports = {
 5 |   entry: {
 6 |     jsnes: "./src/index.js",
 7 |     "jsnes.min": "./src/index.js",
 8 |   },
 9 |   devtool: "source-map",
10 |   output: {
11 |     path: path.resolve(__dirname, "dist"),
12 |     filename: "[name].js",
13 |     library: "jsnes",
14 |     libraryTarget: "umd",
15 |     umdNamedDefine: true,
16 |   },
17 |   module: {
18 |     rules: [
19 |       {
20 |         test: /\.js$/,
21 |         enforce: "pre",
22 |         exclude: /node_modules/,
23 |         use: [
24 |           {
25 |             loader: "eslint-loader",
26 |           },
27 |         ],
28 |       },
29 |     ],
30 |   },
31 |   plugins: [
32 |     new UglifyJsPlugin({
33 |       include: /\.min\.js$/,
34 |       sourceMap: true,
35 |     }),
36 |   ],
37 | };
38 | 


--------------------------------------------------------------------------------