Browse Source

[web] use npm/rollup/babel-based buildsuite, use modern ECMAScript features

master
Adrian Siekierka 1 month ago
parent
commit
3ce6fe4adf
19 changed files with 2203 additions and 1093 deletions
  1. 3
    0
      .gitignore
  2. 2
    0
      web/.babelrc
  3. 0
    33
      web/index.html
  4. BIN
      web/loading.png
  5. 20
    0
      web/package.json
  6. 17
    0
      web/rollup.config.js
  7. 191
    0
      web/src/audio.js
  8. 265
    0
      web/src/emulator.js
  9. 65
    0
      web/src/index.js
  10. 17
    17
      web/src/keymap.js
  11. 221
    0
      web/src/render.js
  12. 24
    0
      web/src/util.js
  13. 136
    0
      web/src/vfs.js
  14. 184
    0
      web/src/vfs_wrapper.js
  15. 1058
    445
      web/zeta.js
  16. 0
    192
      web/zeta_audio.js
  17. 0
    70
      web/zeta_preload.js
  18. 0
    217
      web/zeta_render.js
  19. 0
    119
      web/zeta_vfs.js

+ 3
- 0
.gitignore View File

@@ -1,3 +1,6 @@
1 1
 build
2 2
 res/*.c
3 3
 res/*.h
4
+web/node_modules
5
+web/package-lock.json
6
+web/zeta.js

+ 2
- 0
web/.babelrc View File

@@ -0,0 +1,2 @@
1
+{
2
+}

+ 0
- 33
web/index.html View File

@@ -1,33 +0,0 @@
1
-<!DOCTYPE html>
2
-<html>
3
-<head>
4
-<meta charset=utf-8>
5
-<style type="text/css">
6
-* { border: 0; margin: 0; padding: 0; }
7
-#zeta_canvas { width: 100%; height: 100%; display: block; }
8
-</style>
9
-</head>
10
-<body>
11
-<canvas id="zeta_canvas" width="640" height="350"></canvas>
12
-<script type="text/javascript" src="./zeta_preload.js"></script>
13
-<script type="text/javascript">
14
-Zeta({
15
-	render: {
16
-		canvas: document.getElementById("zeta_canvas")
17
-	},
18
-	dev: false,
19
-	path: "./",
20
-	files: [
21
-		["zzt.zip", function(f) {
22
-			let fl = f.toLowerCase();
23
-			return fl == "zzt.cfg" || fl == "zzt.dat" || fl == "zzt.exe";
24
-		}],
25
-		"my_game.zip"
26
-	],
27
-	arg: "MY_GAME.ZZT"
28
-}, function(zeta) {
29
-
30
-});
31
-</script>
32
-</body>
33
-</html>

BIN
web/loading.png View File


+ 20
- 0
web/package.json View File

@@ -0,0 +1,20 @@
1
+{
2
+  "name": "zeta-web",
3
+  "version": "0.1.0",
4
+  "description": "Zeta web frontend",
5
+  "main": "src/index.js",
6
+  "scripts": {
7
+    "build": "node_modules/.bin/rollup -c"
8
+  },
9
+  "author": "asie",
10
+  "license": "GPL-3.0-or-later",
11
+  "devDependencies": {
12
+    "@babel/core": "^7.5.5",
13
+    "@babel/preset-env": "^7.5.5",
14
+    "rollup": "^1.19.4",
15
+    "rollup-plugin-babel": "^4.3.3",
16
+    "rollup-plugin-node-resolve": "^5.2.0"
17
+  },
18
+  "dependencies": {
19
+  }
20
+}

+ 17
- 0
web/rollup.config.js View File

@@ -0,0 +1,17 @@
1
+import resolve from 'rollup-plugin-node-resolve';
2
+import babel from 'rollup-plugin-babel';
3
+
4
+export default {
5
+  input: './src/index.js',
6
+  plugins: [
7
+    resolve(),
8
+    babel({
9
+      exclude: 'node_modules/**' // only transpile our source code
10
+    })
11
+  ],
12
+  output: {
13
+    file: 'zeta.js',
14
+    format: 'iife', // use browser globals
15
+    sourceMap: true
16
+  }
17
+};

+ 191
- 0
web/src/audio.js View File

@@ -0,0 +1,191 @@
1
+/*!
2
+ * Copyright (c) 2018, 2019 Adrian Siekierka
3
+ *
4
+ * This file is part of Zeta.
5
+ *
6
+ * Zeta is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Zeta is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+import { time_ms } from "./util.js";
21
+
22
+let audioCtx = undefined;
23
+
24
+document.addEventListener('mousedown', function(event) {
25
+	if (audioCtx == undefined) {
26
+		audioCtx = new (window.AudioContext || window.webkitAudioContext) ();
27
+	}
28
+});
29
+
30
+document.addEventListener('keydown', function(event) {
31
+	if (audioCtx == undefined) {
32
+		audioCtx = new (window.AudioContext || window.webkitAudioContext) ();
33
+	}
34
+});
35
+
36
+export class OscillatorBasedAudio {
37
+	constructor() {
38
+		this.lastCurrTime = 0;
39
+		this.lastTimeMs = 0;
40
+		this.timeSpeakerOn = 0;
41
+		this.audioGain = undefined;
42
+		this.pc_speaker = undefined;
43
+	}
44
+
45
+	on(freq) {
46
+		if (audioCtx == undefined)
47
+			return;
48
+
49
+		let cTime = audioCtx.currentTime;
50
+		if (cTime != this.lastCurrTime) {
51
+			this.lastCurrTime = cTime;
52
+			this.lastTimeMs = time_ms();
53
+		}
54
+
55
+		let lastADelay = (time_ms() - this.lastTimeMs) / 1000.0;
56
+
57
+		// console.log("pc speaker " + freq + " " + (audioCtx.currentTime + lastADelay));
58
+		if (this.pc_speaker == undefined) {
59
+			this.audioGain = audioCtx.createGain();
60
+			this.pc_speaker = audioCtx.createOscillator();
61
+			this.pc_speaker.type = 'square';
62
+			this.pc_speaker.frequency.setValueAtTime(freq, audioCtx.currentTime + lastADelay);
63
+			this.pc_speaker.connect(this.audioGain);
64
+			this.audioGain.connect(audioCtx.destination);
65
+			this.audioGain.gain.setValueAtTime(0.2, audioCtx.currentTime);
66
+			this.pc_speaker.start(0);
67
+		} else {
68
+			this.pc_speaker.frequency.setValueAtTime(freq, audioCtx.currentTime + lastADelay);
69
+		}
70
+
71
+		this.timeSpeakerOn = time_ms();
72
+	}
73
+
74
+	off() {
75
+		if (this.pc_speaker == undefined)
76
+			return;
77
+
78
+		let cTime = audioCtx.currentTime;
79
+		if (cTime != this.lastCurrTime) {
80
+			this.lastCurrTime = cTime;
81
+			this.lastTimeMs = time_ms();
82
+		}
83
+
84
+		let lastADelay = (time_ms() - this.lastTimeMs) / 1000.0;
85
+		// console.log("pc speaker off " + (audioCtx.currentTime + lastADelay));
86
+		this.pc_speaker.frequency.setValueAtTime(0, audioCtx.currentTime + lastADelay);
87
+	}
88
+}
89
+
90
+export class BufferBasedAudio {
91
+	constructor(emu, options) {
92
+		this.emu = emu;
93
+		this.pc_speaker = undefined;
94
+		this.sampleRate = (options && options.sampleRate) || 48000;
95
+		this.bufferSize = (options && options.bufferSize) || 4096;
96
+		this.volume = (options && options.volume) || 0.2;
97
+	}
98
+
99
+	_initSpeaker() {
100
+		this.pc_speaker = audioCtx.createBufferSource();
101
+		let buffer = audioCtx.createBuffer(1, this.bufferSize * 2, this.sampleRate);
102
+		this.pc_speaker.buffer = buffer;
103
+		this.pc_speaker.connect(audioCtx.destination);
104
+
105
+		let nativeBuffer = this.emu._malloc(this.bufferSize);
106
+		let nativeHeap = new Uint8Array(this.emu.HEAPU8.buffer, nativeBuffer, this.bufferSize);
107
+
108
+		this.emu._audio_stream_init(time_ms(), this.sampleRate);
109
+		this.emu._audio_stream_set_volume(Math.floor(this.volume * this.emu._audio_stream_get_max_volume()));
110
+		let startedAt = 0;
111
+		let startedAtMs = 0;
112
+		let lastTime = 0;
113
+
114
+		let write = function(offset) {
115
+			let out = buffer.getChannelData(0);
116
+			let sm = Math.min(time_ms(), ((audioCtx.currentTime - startedAt) * 1000) + startedAtMs);
117
+			this.emu._audio_stream_generate_u8(sm, nativeBuffer, this.bufferSize);
118
+			for (let i = 0; i < this.bufferSize; i++) {
119
+				out[offset + i] = (nativeHeap[i] - 127) / 127.0;
120
+			}
121
+		}
122
+
123
+		let tick = function() {
124
+			let ltPos = Math.floor( (lastTime / (this.bufferSize / this.sampleRate)) % 2 );
125
+			let ctPos = Math.floor( (audioCtx.currentTime / (this.bufferSize / this.sampleRate)) % 2 );
126
+			if (ltPos != ctPos) {
127
+				write(ltPos * this.bufferSize);
128
+			}
129
+			lastTime = audioCtx.currentTime;
130
+		};
131
+
132
+		this.pc_speaker.loop = true;
133
+		startedAt = audioCtx.currentTime;
134
+		startedAtMs = time_ms();
135
+		this.pc_speaker.start();
136
+		lastTime = startedAt - 0.001;
137
+
138
+		tick();
139
+		setInterval(tick, (this.bufferSize / this.sampleRate) * (1000.0 / 2));
140
+	}
141
+
142
+	on(freq) {
143
+		if (audioCtx == undefined) return;
144
+		if (this.pc_speaker == undefined) this._initSpeaker();
145
+		this.emu._audio_stream_append_on(time_ms(), freq);
146
+	}
147
+
148
+	off() {
149
+		if (this.pc_speaker == undefined) return;
150
+		this.emu._audio_stream_append_off(time_ms());
151
+	}
152
+}
153
+
154
+/*	ZetaAudio.createScriptProcessorBased = function(emu) {
155
+		var pc_speaker = undefined;
156
+		var emu = emu;
157
+
158
+		var init_speaker = function() {
159
+			pc_speaker = audioCtx.createScriptProcessor();
160
+
161
+			var bufferSize = pc_speaker.bufferSize;
162
+			var buffer = emu._malloc(bufferSize);
163
+			var heap = new Uint8Array(emu.HEAPU8.buffer, buffer, bufferSize);
164
+
165
+			emu._audio_stream_init(time_ms(), Math.floor(audioCtx.sampleRate));
166
+			emu._audio_stream_set_volume(Math.floor(0.2 * emu._audio_stream_get_max_volume()));
167
+
168
+			pc_speaker.onaudioprocess = function(event) {
169
+				var out = event.outputBuffer.getChannelData(0);
170
+				emu._audio_stream_generate_u8(time_ms() - (bufferSize * 1000 / audioCtx.sampleRate), buffer, bufferSize);
171
+				for (var i = 0; i < bufferSize; i++) {
172
+					out[i] = (heap[i] - 127) / 127.0;
173
+				}
174
+				console.log("audio " + bufferSize);
175
+			};
176
+
177
+			pc_speaker.connect(audioCtx.destination);
178
+		};
179
+
180
+		return {
181
+			on: function(freq) {
182
+				if (audioCtx == undefined) return;
183
+				if (pc_speaker == undefined) init_speaker();
184
+				emu._audio_stream_append_on(time_ms(), freq);
185
+			},
186
+			off: function() {
187
+				if (pc_speaker == undefined) return;
188
+				emu._audio_stream_append_off(time_ms());
189
+			}
190
+		};
191
+	}; */

+ 265
- 0
web/src/emulator.js View File

@@ -0,0 +1,265 @@
1
+/*!
2
+ * Copyright (c) 2018, 2019 Adrian Siekierka
3
+ *
4
+ * This file is part of Zeta.
5
+ *
6
+ * Zeta is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Zeta is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+import { time_ms } from "./util.js";
21
+import { keymap } from "./keymap.js";
22
+import { initVfsWrapper, setWrappedEmu, setWrappedVfs } from "./vfs_wrapper.js"
23
+
24
+/* var check_modifiers = function(event) {
25
+	if (event.shiftKey) emu._zzt_kmod_set(0x01); else emu._zzt_kmod_clear(0x01);
26
+	if (event.ctrlKey) emu._zzt_kmod_set(0x04); else emu._zzt_kmod_clear(0x04);
27
+	if (event.altKey) emu._zzt_kmod_set(0x08); else emu._zzt_kmod_clear(0x08);
28
+} */
29
+
30
+class Emulator {
31
+    constructor(element, emu, render, audio, vfs, options) {
32
+        this.element = element;
33
+        this.emu = emu;
34
+        this.render = render;
35
+        this.audio = audio;
36
+        this.vfs = vfs;
37
+
38
+        this.mouseSensitivity = (options && options.mouseSensitivity) || 4;
39
+
40
+        this.frameQueued = false;
41
+        this.time_ms_cached = time_ms();
42
+        this.last_timer_time = 0;
43
+        this.timer_dur = 1000 / 18.2;
44
+        this.opcodes = 1000;
45
+
46
+        const self = this;
47
+
48
+        window.vfsg_time_ms = function() {
49
+            return self.time_ms_cached;
50
+        }
51
+
52
+        window.vfsg_has_feature = function(id) {
53
+            if (id == 1 /* joy connected */) return true;
54
+            else if (id == 2 /* mouse connected */) return true;
55
+            else return false;
56
+        }        
57
+        
58
+        window.zetag_update_charset = function(width, height, char_ptr) {
59
+	        const data = new Uint8Array(emu.HEAPU8.buffer, char_ptr, 256 * height);
60
+	        render.setCharset(width, height, data);
61
+        }
62
+
63
+        window.zetag_update_palette = function(palette_ptr) {
64
+	        const data = new Uint32Array(emu.HEAPU32.buffer, palette_ptr, 16);
65
+        	render.setPalette(data);
66
+        }
67
+
68
+        window.speakerg_on = function(freq) {
69
+        	if (!document.hasFocus()) {
70
+		        speakerg_off();
71
+          		return;
72
+	        }
73
+
74
+            if (audio != undefined) audio.on(freq);
75
+        }
76
+
77
+        window.speakerg_off = function() {
78
+	        if (audio != undefined) audio.off();
79
+        }
80
+
81
+        window.addEventListener("message", function(event) {
82
+            if (event.data == "zzt_tick") {
83
+                event.stopPropagation();
84
+                self._tick();
85
+            }
86
+        }, true);
87
+
88
+        document.addEventListener('keydown', function(event) {
89
+            if (event.target != element) return false;
90
+            let ret = false;
91
+
92
+            if (event.key == "Shift") emu._zzt_kmod_set(0x01);
93
+            else if (event.key == "Control") emu._zzt_kmod_set(0x04);
94
+            else if (event.key == "Alt" || event.key == "AltGraph") emu._zzt_kmod_set(0x08);
95
+            else ret = false;
96
+
97
+            let chr = (event.key.length == 1) ? event.key.charCodeAt(0) : (event.keyCode < 32 ? event.keyCode : 0);
98
+            let key = keymap[event.key] || 0;
99
+            if (key >= 0x46 && key <= 0x53) chr = 0;
100
+            if (chr > 0 || key > 0) {
101
+                emu._zzt_key(chr, key);
102
+                ret = true;
103
+            }
104
+
105
+            if (ret) {
106
+                event.preventDefault();
107
+            }
108
+            return false;
109
+        }, false);
110
+
111
+        document.addEventListener('keyup', function(event) {
112
+            if (event.target != element) return false;
113
+            let ret = true;
114
+
115
+            if (event.key == "Shift") emu._zzt_kmod_clear(0x01);
116
+            else if (event.key == "Control") emu._zzt_kmod_clear(0x04);
117
+            else if (event.key == "Alt" || event.key == "AltGraph") emu._zzt_kmod_clear(0x08);
118
+            else ret = false;
119
+
120
+            var key = keymap[event.key] || 0;
121
+            if (key > 0) {
122
+                emu._zzt_keyup(key);
123
+                ret = true;
124
+            }
125
+
126
+            if (ret) {
127
+                event.preventDefault();
128
+            }
129
+            return false;
130
+        }, false);
131
+
132
+        this.element.addEventListener("mousemove", function(e) {
133
+            if (emu == undefined) return;
134
+    
135
+            const mx = e.movementX * self.mouseSensitivity;
136
+            const my = e.movementY * self.mouseSensitivity;
137
+            emu._zzt_mouse_axis(0, mx);
138
+            emu._zzt_mouse_axis(1, my);
139
+    /*		var mouseX = e.clientX - o.offsetLeft;
140
+            var mouseY = e.clientY - o.offsetTop;
141
+            if (mouseX < 0) mouseX = 0;
142
+            else if (mouseX >= 640) mouseX = 639;
143
+            if (mouseY < 0) mouseY = 0;
144
+            else if (mouseY >= 350) mouseY = 349; */
145
+        });
146
+    
147
+        this.element.addEventListener("mousedown", function(e) {
148
+            element.requestPointerLock();
149
+    
150
+            if (emu == undefined) return;
151
+            emu._zzt_mouse_set(e.button);
152
+        });
153
+    
154
+        this.element.addEventListener("mouseup", function(e) {
155
+            if (emu == undefined) return;
156
+            emu._zzt_mouse_clear(e.button);
157
+        });
158
+    }
159
+
160
+    _frame() {
161
+        this._pollGamepads();
162
+
163
+        const ptr = this.emu._zzt_get_ram();
164
+        const heap = new Uint8Array(this.emu.HEAPU8.buffer, ptr + 0xB8000, 80*25*2);
165
+
166
+        this.render.render(heap, this.emu._zzt_video_mode(), this.time_ms_cached);
167
+
168
+        this.emu._zzt_mark_frame();
169
+        this.frameQueued = false;
170
+    }
171
+
172
+    _resetLastTimerTime() {
173
+        this.last_timer_time = time_ms();
174
+    }
175
+
176
+    _tick() {
177
+        this.time_ms_cached = time_ms();
178
+
179
+        while ((this.time_ms_cached - this.last_timer_time) >= this.timer_dur) {
180
+//    		console.log("timer, drift = " + (tms - last_timer_time - timer_dur) + " ms");
181
+            this.last_timer_time += this.timer_dur;
182
+            this.emu._zzt_mark_timer();
183
+        }
184
+
185
+        const rcode = this.emu._zzt_execute(this.opcodes);
186
+        const duration = time_ms() - this.time_ms_cached;
187
+        if (rcode) {
188
+            if (rcode == 1) {
189
+                if (duration < 3) {
190
+                    this.opcodes = (this.opcodes * 20 / 19);
191
+                } else if (duration > 6) {
192
+                    this.opcodes = (this.opcodes * 19 / 20);
193
+                }
194
+            }
195
+
196
+            if (!this.frameQueued) {
197
+                this.frameQueued = true;
198
+                window.requestAnimationFrame(() => this._frame());
199
+            }
200
+
201
+            const time_to_timer = this.timer_dur - ((this.time_ms_cached + duration) - this.last_timer_time);
202
+            if (rcode != 3 || time_to_timer <= 1) {
203
+                window.postMessage("zzt_tick", "*");
204
+            } else {
205
+                setTimeout(() => this._tick(), time_to_timer);
206
+            }
207
+        }
208
+    }
209
+
210
+    _pollGamepads() {
211
+        const gamepads = navigator.getGamepads();
212
+        for (var i = 0; i < gamepads.length; i++) {
213
+            const gamepad = gamepads[i];
214
+            if (gamepad != null && gamepad.axes.length >= 2 && gamepad.buttons.length >= 1) {
215
+                const ax0 = Math.round(gamepad.axes[0] * 127);
216
+                const ax1 = Math.round(gamepad.axes[1] * 127);
217
+                this.emu._zzt_joy_axis(0, ax0);
218
+                this.emu._zzt_joy_axis(1, ax1);
219
+                this.emu._zzt_joy_clear(0);
220
+                for (var j = 0; j < gamepad.buttons.length; j++) {
221
+                    if (gamepad.buttons[j].pressed) {
222
+                        this.emu._zzt_joy_set(0);
223
+                        break;
224
+                    }
225
+                }
226
+            }
227
+        }
228
+    }
229
+}
230
+
231
+export function createEmulator(render, audio, vfs, options) {
232
+	return new Promise(resolve => {
233
+        ZetaNative().then(emu => {
234
+            const vfs_arg = (options && options.arg) || "";
235
+            const buffer = emu._malloc(vfs_arg.length + 1);
236
+            const heap = new Uint8Array(emu.HEAPU8.buffer, buffer, vfs_arg.length + 1);
237
+            for (var i = 0; i < vfs_arg.length; i++) {
238
+                heap[i] = vfs_arg.charCodeAt(i);
239
+            }
240
+            heap[vfs_arg.length] = 0;
241
+            
242
+            setWrappedVfs(vfs);
243
+            setWrappedEmu(emu);
244
+            initVfsWrapper();
245
+
246
+            const emuObj = new Emulator(options.render.canvas, emu, render, audio, vfs, options);
247
+
248
+            emu._zzt_init();
249
+
250
+            let handle = vfsg_open("zzt.exe", 0);
251
+            if (handle < 0)
252
+                handle = vfsg_open("superz.exe", 0);
253
+            if (handle < 0)
254
+                throw "Could not find ZZT executable!";
255
+            emu._zzt_load_binary(handle, buffer);
256
+            vfsg_close(handle);
257
+
258
+            emu._zzt_set_timer_offset(Date.now() % 86400000)
259
+            
260
+            emuObj._resetLastTimerTime();
261
+            emuObj._tick();
262
+            resolve(emuObj);
263
+        });
264
+    });
265
+}

+ 65
- 0
web/src/index.js View File

@@ -0,0 +1,65 @@
1
+/*!
2
+ * Copyright (c) 2018, 2019 Adrian Siekierka
3
+ *
4
+ * This file is part of Zeta.
5
+ *
6
+ * Zeta is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Zeta is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+import { OscillatorBasedAudio } from "./audio.js";
21
+import { CanvasBasedRenderer } from "./render.js";
22
+import { createVfsFromMap, createVfsFromVfs, createVfsFromZip } from "./vfs.js";
23
+import { createEmulator } from "./emulator.js";
24
+
25
+window.ZetaInitialize = function(options) {
26
+    if (!options.render) throw "Missing option: render!";
27
+	if (!options.render.canvas) throw "Missing option: render.canvas!";
28
+	if (!options.path) throw "Missing option: path!";
29
+    if (!options.files) throw "Missing option: files!";
30
+
31
+	const canvas = options.render.canvas;
32
+	canvas.contentEditable = true;
33
+	const ctx = canvas.getContext('2d', {alpha: false});
34
+	ctx.imageSmoothingEnabled = false;
35
+
36
+    // TODO: bring back loading screen
37
+
38
+    var vfsPromises = [];
39
+    var vfsProgresses = [];
40
+    var vfsObjects = [];
41
+
42
+	for (var s in options.files) {
43
+        vfsProgresses.push(0);
44
+        const file = options.files[s]
45
+		if (Array.isArray(file)) {
46
+            vfsPromises.push(
47
+                createVfsFromZip(file[0], file[1], null)
48
+                    .then(o => vfsObjects.push(o))
49
+            );
50
+        } else {
51
+            vfsPromises.push(
52
+                createVfsFromZip(file, null, null)
53
+                    .then(o => vfsObjects.push(o))
54
+            );
55
+        }
56
+	}
57
+
58
+	return Promise.all(vfsPromises).then(_ => {
59
+        const render = new CanvasBasedRenderer(options.render.canvas);
60
+        const audio = new OscillatorBasedAudio();
61
+        const vfs = createVfsFromVfs(vfsObjects);
62
+
63
+        return createEmulator(render, audio, vfs, options);
64
+    }).then(_ => true);
65
+}

web/zeta_kbdmap.js → web/src/keymap.js View File

@@ -17,7 +17,7 @@
17 17
  * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18 18
  */
19 19
 
20
-ZetaKbdmap = {
20
+let ZetaKbdmap = {
21 21
 	ArrowUp: 0x48,
22 22
 	ArrowLeft: 0x4B,
23 23
 	ArrowRight: 0x4D,
@@ -47,22 +47,22 @@ ZetaKbdmap = {
47 47
 	" ": 57
48 48
 };
49 49
 
50
-(function() {
51
-	var addCharsInOrder = function(c, off) {
52
-		for(var i = 0; i < c.length; i++) {
53
-			ZetaKbdmap[c.charAt(i)]=off + i;
54
-		}
50
+let addCharsInOrder = function(c, off) {
51
+	for(var i = 0; i < c.length; i++) {
52
+		ZetaKbdmap[c.charAt(i)]=off + i;
55 53
 	}
54
+}
56 55
 
57
-	addCharsInOrder("1234567890", 2);
58
-	addCharsInOrder("QWERTYUIOP{}", 16);
59
-	addCharsInOrder("qwertyuiop[]", 16);
60
-	addCharsInOrder("asdfghjkl;'", 30);
61
-	addCharsInOrder("ASDFGHJKL:\"", 30);
62
-	addCharsInOrder("zxcvbnm,./", 44);
63
-	addCharsInOrder("ZXCVBNM<>?", 44);
56
+addCharsInOrder("1234567890", 2);
57
+addCharsInOrder("QWERTYUIOP{}", 16);
58
+addCharsInOrder("qwertyuiop[]", 16);
59
+addCharsInOrder("asdfghjkl;'", 30);
60
+addCharsInOrder("ASDFGHJKL:\"", 30);
61
+addCharsInOrder("zxcvbnm,./", 44);
62
+addCharsInOrder("ZXCVBNM<>?", 44);
64 63
 
65
-	for (var i = 1; i <= 10; i++) {
66
-		ZetaKbdmap["F" + i] = 0x3A + i;
67
-	}
68
-})();
64
+for (var i = 1; i <= 10; i++) {
65
+	ZetaKbdmap["F" + i] = 0x3A + i;
66
+}
67
+
68
+export const keymap = ZetaKbdmap;

+ 221
- 0
web/src/render.js View File

@@ -0,0 +1,221 @@
1
+/*!
2
+ * Copyright (c) 2018, 2019 Adrian Siekierka
3
+ *
4
+ * This file is part of Zeta.
5
+ *
6
+ * Zeta is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Zeta is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+export class CanvasBasedRenderer {
21
+	constructor(canvas, options) {
22
+		this.canvas = canvas;
23
+		this.blink_duration = (options && options.blink_duration) || 466;
24
+
25
+		this.ctx = canvas.getContext('2d', {alpha: false});
26
+		this.ctx.imageSmoothingEnabled = false;
27
+
28
+		this.video_blink = true;
29
+		if (options && options.hasOwnProperty("blink")) {
30
+			this.video_blink = options.blink;
31
+		}
32
+
33
+		this.video_mode = -1;
34
+		this.chrBuf = [];
35
+		this.drawChrWidth = undefined;
36
+		this.chrWidth = undefined;
37
+		this.scrWidth = undefined;
38
+		this.chrHeight = undefined;
39
+		this.scrHeight = undefined;
40
+		this.pw = undefined;
41
+		this.ph = undefined;
42
+		this.cw = undefined;
43
+		this.ch = undefined;
44
+		this.scale = 1;
45
+
46
+		this.asciiFg = null;
47
+		this.charset = null;
48
+		this.palette = null;
49
+		this.rdDirty = false;
50
+
51
+		this.charset_override_enabled = (options && options.charset_override) || false;
52
+		this.charset_override = null;
53
+		if (this.charset_override_enabled) {
54
+			let coImg = new Image();
55
+			coImg.src = options.charset_override;
56
+			coImg.onload = function() {
57
+				this.charset_override = coImg;
58
+				this.rdDirty = true;
59
+			};
60
+		}
61
+	}
62
+
63
+	_updVideoMode(val) {
64
+		if (val != this.video_mode) {
65
+			this.chrBuf = [];
66
+			if ((val & 0x02) == 2) {
67
+				this.scrWidth = 80;
68
+			} else {
69
+				this.scrWidth = 40;
70
+			}
71
+			this.scrHeight = 25;
72
+			this.video_mode = val;
73
+		}
74
+
75
+		this.drawChrWidth = this.chrWidth * (80 / this.scrWidth);
76
+
77
+		this.pw = this.scrWidth*this.drawChrWidth;
78
+		this.ph = this.scrHeight*this.chrHeight;
79
+		this.cw = this.canvas.width;
80
+		this.ch = this.canvas.height;
81
+		this.scale = Math.min(Math.floor(this.cw / this.pw), Math.floor(this.ch / this.ph));
82
+	}
83
+
84
+	_drawChar(x, y, chr, col, time) {
85
+		if (this.video_blink && col >= 0x80) {
86
+			col = col & 0x7F;
87
+
88
+			if ((time % this.blink_duration) >= (this.blink_duration / 2)) {
89
+				col = (col >> 4) * 0x11;
90
+			}
91
+		}
92
+
93
+		let buffered = this.chrBuf[y * 80 + x];
94
+		let bufcmp = (chr << 8) | col;
95
+
96
+		if (buffered == bufcmp) {
97
+			return;
98
+		} else {
99
+			this.chrBuf[y * 80 + x] = bufcmp;
100
+		}
101
+
102
+		x = x * this.drawChrWidth;
103
+		y = y * this.chrHeight;
104
+
105
+		let bg = (col >> 4) & 0x0F;
106
+		let fg = (col & 15);
107
+
108
+		let rw = this.drawChrWidth;
109
+		let rh = this.chrHeight;
110
+
111
+		if (this.scale > 1) {
112
+			rw *= this.scale;
113
+			rh *= this.scale;
114
+			x = (x*this.scale) + ((this.cw - this.pw*this.scale) / 2);
115
+			y = (y*this.scale) + ((this.ch - this.ph*this.scale) / 2);
116
+		} else {
117
+			x += ((this.cw - this.pw) / 2);
118
+			y += ((this.ch - this.ph) / 2);
119
+		}
120
+
121
+		this.ctx.fillStyle = this.palette[bg];
122
+		this.ctx.fillRect(x, y, rw, rh);
123
+
124
+		if (bg != fg) {
125
+			this.ctx.drawImage(this.asciiFg[fg], (chr & 15) * this.chrWidth, ((chr >> 4) & 15) * this.chrHeight, this.chrWidth, this.chrHeight, x, y, rw, rh);
126
+		}
127
+	}
128
+
129
+	_updRenderData() {
130
+		if (this.palette == null) {
131
+			return;
132
+		}
133
+
134
+		let srcImg = null;
135
+		if (this.charset_override_enabled) {
136
+			if (this.charset_override == null) return;
137
+			else srcImg = this.charset_override;
138
+		} else {
139
+			if (this.charset == null) return;
140
+		}
141
+
142
+		this.asciiFg = [];
143
+
144
+		if (srcImg == null) {
145
+			let charCanvas = document.createElement('canvas');
146
+			charCanvas.width = 16 * this.chrWidth;
147
+			charCanvas.height = 16 * this.chrHeight;
148
+			let charCtx = charCanvas.getContext('2d');
149
+			let charData = charCtx.createImageData(charCanvas.width, charCanvas.height);
150
+			let data = charData.data;
151
+			let dpos = 0;
152
+
153
+			for (var ch = 0; ch < 256; ch++) {
154
+				let rx = (ch & 0xF) * this.chrWidth;
155
+				let ry = (ch >> 4) * this.chrHeight;
156
+				for (var cy = 0; cy < this.chrHeight; cy++, dpos++) {
157
+					var co = ((ry + cy) * charCanvas.width) + rx;
158
+					var ctmp = this.charset[dpos];
159
+					for (var cx = 0; cx < this.chrWidth; cx++, co++, ctmp = ctmp << 1) {
160
+						var cc = ((ctmp >> 7) & 0x01) * 255;
161
+						data[co * 4 + 0] = cc;
162
+						data[co * 4 + 1] = cc;
163
+						data[co * 4 + 2] = cc;
164
+						data[co * 4 + 3] = cc;
165
+					}
166
+				}
167
+			}
168
+
169
+			charCtx.putImageData(charData, 0, 0);
170
+			srcImg = charCanvas;
171
+		}
172
+
173
+		this.asciiFg[15] = srcImg;
174
+		for (var i = 0; i < 15; i++) {
175
+			let charCanvas = document.createElement('canvas');
176
+			charCanvas.width = 16 * this.chrWidth;
177
+			charCanvas.height = 16 * this.chrHeight;
178
+			let charCtx = charCanvas.getContext('2d');
179
+			charCtx.globalCompositeOperation = 'copy';
180
+			charCtx.drawImage(srcImg, 0, 0);
181
+			charCtx.globalCompositeOperation = 'source-in';
182
+			charCtx.fillStyle = this.palette[i];
183
+			charCtx.fillRect(0, 0, charCanvas.width, charCanvas.height);
184
+			this.asciiFg[i] = charCanvas;
185
+		}
186
+
187
+		this.rdDirty = false;
188
+	};
189
+
190
+	render(heap, mode, time) {
191
+		if (this.rdDirty) this._updRenderData();
192
+		this._updVideoMode(mode);
193
+
194
+		let pos = 0;
195
+
196
+		if (this.asciiFg != null && this.palette != null) {
197
+			for (var y = 0; y < 25; y++) {
198
+				for (var x = 0; x < this.scrWidth; x++, pos+=2) {
199
+					this._drawChar(x, y, heap[pos], heap[pos+1], time);
200
+				}
201
+			}
202
+		}
203
+	}
204
+
205
+	setCharset(width, height, heap) {
206
+		this.chrWidth = width;
207
+		this.chrHeight = height;
208
+		this.charset = heap;
209
+		this.rdDirty = true;
210
+	}
211
+
212
+	setPalette(heap) {
213
+		this.palette = new Array();
214
+		for (var i = 0; i < 16; i++) {
215
+			let s = (heap[i] & 0xFFFFFF).toString(16);
216
+			while (s.length < 6) s = "0" + s;
217
+			this.palette[i] = "#" + s;
218
+		}
219
+		this.rdDirty = true;
220
+	}
221
+}

+ 24
- 0
web/src/util.js View File

@@ -0,0 +1,24 @@
1
+/*!
2
+ * Copyright (c) 2018, 2019 Adrian Siekierka
3
+ *
4
+ * This file is part of Zeta.
5
+ *
6
+ * Zeta is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Zeta is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+let date_s = Date.now();
21
+
22
+export function time_ms() {
23
+	return Date.now() - date_s;
24
+}

+ 136
- 0
web/src/vfs.js View File

@@ -0,0 +1,136 @@
1
+/*!
2
+ * Copyright (c) 2018, 2019 Adrian Siekierka
3
+ *
4
+ * This file is part of Zeta.
5
+ *
6
+ * Zeta is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Zeta is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+class MapBasedVfs {
21
+	constructor(inputMap, options) {
22
+		this.map = {};
23
+		for (var key in inputMap) {
24
+			this.map[key.toUpperCase()] = inputMap[key];
25
+		}
26
+		this.readonly = (options && options.readonly) || false;
27
+	}
28
+
29
+	canSet(key) {
30
+		return this.readonly;
31
+	}
32
+
33
+	get(key) {
34
+		if (!this.map.hasOwnProperty(key.toUpperCase())) {
35
+			return null;
36
+		}
37
+		return this.map[key.toUpperCase()].slice(0);
38
+	}
39
+
40
+	list(filter) {
41
+		let a = [];
42
+
43
+		for (var key in this.map) {
44
+			if (filter == null || filter(key)) {
45
+				a.push(key);
46
+			}
47
+		}
48
+
49
+		return a;
50
+	}
51
+
52
+	set(key, value) {
53
+		if (this.readonly) return false;
54
+		this.map[key.toUpperCase()] = value;
55
+		return true;
56
+	}
57
+}
58
+
59
+class VfsBasedVfs {
60
+	constructor(providers) {
61
+		this.providers = providers;
62
+	}
63
+
64
+	canSet(key) {
65
+		for (var p = 0; p < this.providers.length; p++) {
66
+			let provider = this.providers[p];
67
+			if (provider.canSet(key)) {
68
+				return true;
69
+			}
70
+		}
71
+		return false;
72
+	}
73
+
74
+	get(key) {
75
+		for (var p = this.providers.length - 1; p >= 0; p--) {
76
+			let provider = this.providers[p];
77
+			const result = provider.get(key);
78
+			if (result != null) return result;
79
+		}
80
+		return null;
81
+	}
82
+
83
+	list(filter) {
84
+		let data = [];
85
+		for (var p = 0; p < this.providers.length; p++) {
86
+			let provider = this.providers[p];
87
+			data = data.concat(provider.list(filter));
88
+		}
89
+		return data.sort();
90
+	}
91
+
92
+	set(key, value) {
93
+		let promise = Promise.resolve(false);
94
+		for (var p = this.providers.length - 1; p >= 0; p--) {
95
+			let provider = this.providers[p];
96
+			if (provider.set(key, value)) return true;
97
+		}
98
+		return false;
99
+	}
100
+}
101
+
102
+export function createVfsFromMap(inputMap, options) {
103
+	return new MapBasedVfs(inputMap, options);
104
+}
105
+
106
+export function createVfsFromVfs(providers) {
107
+	return new VfsBasedVfs(providers);
108
+}
109
+
110
+export function createVfsFromZip(url, options, progressCallback) {
111
+	return new Promise(resolve => {
112
+		var xhr = new XMLHttpRequest();
113
+		xhr.open("GET", url, true);
114
+		xhr.responseType = "arraybuffer";
115
+
116
+		xhr.onprogress = function(event) {
117
+			if (progressCallback != null) progressCallback(event.loaded / event.total);
118
+		};
119
+
120
+		xhr.onload = function() {
121
+			console.log(this.response);
122
+			if (progressCallback != null) progressCallback(1);
123
+
124
+			let files = UZIP.parse(this.response);
125
+			let fileMap = {};
126
+			for (var key in files) {
127
+				if (!(options && options.filenameFilter) || options.filenameFilter(key)) {
128
+					fileMap[key] = files[key];
129
+				}
130
+			}
131
+			resolve(new MapBasedVfs(fileMap, options));
132
+		};
133
+
134
+		xhr.send();
135
+	});
136
+}

+ 184
- 0
web/src/vfs_wrapper.js View File

@@ -0,0 +1,184 @@
1
+/*!
2
+ * Copyright (c) 2018, 2019 Adrian Siekierka
3
+ *
4
+ * This file is part of Zeta.
5
+ *
6
+ * Zeta is free software: you can redistribute it and/or modify
7
+ * it under the terms of the GNU General Public License as published by
8
+ * the Free Software Foundation, either version 3 of the License, or
9
+ * (at your option) any later version.
10
+ *
11
+ * Zeta is distributed in the hope that it will be useful,
12
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
+ * GNU General Public License for more details.
15
+ *
16
+ * You should have received a copy of the GNU General Public License
17
+ * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
+ */
19
+
20
+ import { createVfsFromMap } from "./vfs"
21
+
22
+let emu;
23
+let vfs = createVfsFromMap({});
24
+let handles = {};
25
+
26
+export function setWrappedEmu(inputEmu) {
27
+	emu = inputEmu;
28
+}
29
+
30
+export function setWrappedVfs(inputVfs) {
31
+    vfs = inputVfs;
32
+}
33
+
34
+export function initVfsWrapper() {
35
+	window.vfsg_open = function(fn, mode) {
36
+		if (typeof fn !== "string") {
37
+			fn = emu.AsciiToString(fn);
38
+		}
39
+	
40
+		fn = fn.toUpperCase();
41
+		const data = vfs.get(fn);
42
+		const is_write = (mode & 0x3) == 1;
43
+	
44
+		if (is_write) {
45
+			if (!vfs.canSet(fn)) return -1;
46
+			if (data == null || ((mode & 0x10000) != 0)) {
47
+				data = new Uint8Array(0);
48
+			}
49
+		} else {
50
+			if (data == null) return -1;
51
+		}
52
+	
53
+		console.log("opening " + fn);
54
+		let i = 1;
55
+		while (i in handles) i++;
56
+		handles[i] = {fn: fn, pos: 0, mode: mode, write_on_close: is_write, array: data};
57
+	
58
+		return i;
59
+	}
60
+	
61
+	window.vfsg_close = function(h) {
62
+		if (h in handles) {
63
+			if (handles[h].write_on_close) {
64
+				vfs.set(handles[h].fn, handles[h].array);
65
+			}
66
+			delete handles[h];
67
+			return 0;
68
+		} else return -1;
69
+	}
70
+	
71
+	window.vfsg_seek = function(h, offset, type) {
72
+		if (!(h in handles)) return -1;
73
+		let newpos;
74
+		switch (type) {
75
+			case 0: /* SEEK_SET */
76
+				newpos = offset;
77
+				break;
78
+			case 1: /* SEEK_CUR */
79
+				newpos = handles[h].pos + offset;
80
+				break;
81
+			case 2: /* SEEK_END */
82
+				newpos = handles[h].array.length + offset;
83
+				break;
84
+		}
85
+		console.log("seek to " + newpos);
86
+		if (newpos > handles[h].array.length)
87
+			newpos = handles[h].array.length;
88
+		else if (newpos < 0)
89
+			newpos = 0;
90
+		handles[h].pos = newpos;
91
+		return 0;
92
+	}
93
+	
94
+	window.vfsg_read = function(h, ptr, amount) {
95
+		if (!(h in handles)) return -1;
96
+		h = handles[h];
97
+		let maxlen = Math.min(amount, h.array.length - h.pos);
98
+		console.log("reading " + maxlen + " bytes from " + h.pos + " to " + ptr);
99
+		const heap = new Uint8Array(emu.HEAPU8.buffer, ptr, maxlen);
100
+		for (var pos = 0; pos < maxlen; pos++) {
101
+			heap[pos] = h.array[h.pos + pos];
102
+		}
103
+		console.log("read " + maxlen + " bytes");
104
+		h.pos += maxlen;
105
+		return maxlen;
106
+	}
107
+	
108
+	window.vfsg_write = function(h, ptr, amount) {
109
+		if (!(h in handles)) return -1;
110
+		h = handles[h];
111
+		let len = h.array.length;
112
+		let newlen = h.pos + amount;
113
+		if (newlen > len) {
114
+			var newA = new Uint8Array(newlen);
115
+			newA.set(h.array, 0);
116
+			h.array = newA;
117
+			len = newlen;
118
+		}
119
+		let heap = new Uint8Array(emu.HEAPU8.buffer, ptr, amount);
120
+		for (var pos = 0; pos < amount; pos++) {
121
+			h.array[h.pos + pos] = heap[pos];
122
+		}
123
+		console.log("wrote " + amount + " bytes");
124
+		h.pos += amount;
125
+		return amount;
126
+	}
127
+	
128
+	var ff_list = [];
129
+	var ff_pos = 0;
130
+	
131
+	var vfs_list = function(spec) {
132
+		spec = spec.toUpperCase();
133
+		if (spec.startsWith("*.")) {
134
+			let suffix = spec.substring(1);
135
+			let list = vfs.list(key => key.endsWith(suffix));
136
+			list.sort((a, b) => {
137
+				const lenDiff = a.length - b.length;
138
+				if (lenDiff != 0) return lenDiff;
139
+				else return a.localeCompare(b);
140
+			});
141
+			return list;
142
+		} else {
143
+			console.log("unknown findfirst spec: " + spec);
144
+			return null;
145
+		}
146
+	}
147
+	
148
+	window.vfsg_findfirst = function(ptr, mask, spec) {
149
+		spec = emu.AsciiToString(spec);
150
+		ff_list = [];
151
+		const l = vfs_list(spec);
152
+		if (l == null) return -1;
153
+		ff_list = l;
154
+		ff_pos = 0;
155
+		return vfsg_findnext(ptr);
156
+	}
157
+	
158
+	window.vfsg_findnext = function(ptr) {
159
+		if (ff_pos >= ff_list.length) return -1;
160
+		const finddata = new Uint8Array(emu.HEAPU8.buffer, ptr, 0x100);
161
+	
162
+		// write documented fields
163
+		finddata[0x15] = 0;
164
+		finddata[0x16] = 0;
165
+		finddata[0x17] = 0;
166
+		finddata[0x18] = 0;
167
+		finddata[0x19] = 0;
168
+	
169
+		let fn = ff_list[ff_pos];
170
+		let size = vfs.get(fn).byteLength;
171
+		finddata[0x1A] = size & 0xFF;
172
+		finddata[0x1B] = (size >> 8) & 0xFF;
173
+		finddata[0x1C] = (size >> 16) & 0xFF;
174
+		finddata[0x1D] = (size >> 24) & 0xFF;
175
+	
176
+		for (var i = 0; i < fn.length; i++) {
177
+			finddata[0x1E + i] = fn.charCodeAt(i);
178
+		}
179
+		finddata[0x1E + fn.length] = 0;
180
+	
181
+		ff_pos = ff_pos + 1;
182
+		return 0;
183
+	}	
184
+}

+ 1058
- 445
web/zeta.js
File diff suppressed because it is too large
View File


+ 0
- 192
web/zeta_audio.js View File

@@ -1,192 +0,0 @@
1
-/*!
2
- * Copyright (c) 2018, 2019 Adrian Siekierka
3
- *
4
- * This file is part of Zeta.
5
- *
6
- * Zeta is free software: you can redistribute it and/or modify
7
- * it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Zeta is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
- */
19
-
20
-ZetaAudio = {};
21
-
22
-(function() {
23
-	var audioCtx = undefined;
24
-
25
-	document.addEventListener('mousedown', function(event) {
26
-		if (audioCtx == undefined) {
27
-			audioCtx = new (window.AudioContext || window.webkitAudioContext) ();
28
-		}
29
-	});
30
-
31
-	document.addEventListener('keydown', function(event) {
32
-		if (audioCtx == undefined) {
33
-			audioCtx = new (window.AudioContext || window.webkitAudioContext) ();
34
-		}
35
-	});
36
-
37
-	ZetaAudio.createOscillatorBased = function() {
38
-		var lastCurrTime = 0;
39
-		var lastTimeMs = 0;
40
-		var timeSpeakerOn = 0;
41
-		var audioGain = undefined;
42
-		var pc_speaker = undefined;
43
-
44
-		return {
45
-			on: function(freq) {
46
-				if (audioCtx == undefined)
47
-					return;
48
-
49
-				var cTime = audioCtx.currentTime;
50
-				if (cTime != lastCurrTime) {
51
-					lastCurrTime = cTime;
52
-					lastTimeMs = time_ms();
53
-				}
54
-
55
-				var lastADelay = (time_ms() - lastTimeMs) / 1000.0;
56
-
57
-			//	console.log("pc speaker " + freq + " " + (audioCtx.currentTime + lastADelay));
58
-				if (pc_speaker == undefined) {
59
-					audioGain = audioCtx.createGain();
60
-					pc_speaker = audioCtx.createOscillator();
61
-					pc_speaker.type = 'square';
62
-					pc_speaker.frequency.setValueAtTime(freq, audioCtx.currentTime + lastADelay);
63
-					pc_speaker.connect(audioGain);
64
-					audioGain.connect(audioCtx.destination);
65
-					audioGain.gain.setValueAtTime(0.2, audioCtx.currentTime);
66
-					pc_speaker.start(0);
67
-				} else {
68
-					pc_speaker.frequency.setValueAtTime(freq, audioCtx.currentTime + lastADelay);
69
-				}
70
-
71
-				timeSpeakerOn = time_ms();
72
-			},
73
-
74
-			off: function() {
75
-				if (pc_speaker == undefined)
76
-					return;
77
-
78
-				var cTime = audioCtx.currentTime;
79
-				if (cTime != lastCurrTime) {
80
-					lastCurrTime = cTime;
81
-					lastTimeMs = time_ms();
82
-				}
83
-
84
-				var lastADelay = (time_ms() - lastTimeMs) / 1000.0;
85
-			//	console.log("pc speaker off " + (audioCtx.currentTime + lastADelay));
86
-				pc_speaker.frequency.setValueAtTime(0, audioCtx.currentTime + lastADelay);
87
-			}
88
-		};
89
-	};
90
-
91
-	ZetaAudio.createBufferBased = function(emu, options) {
92
-		var pc_speaker = undefined;
93
-		var emu = emu;
94
-		var sampleRate = (options && options.sampleRate) || 48000;
95
-		var bufferSize = (options && options.bufferSize) || 4096;
96
-
97
-		var init_speaker = function() {
98
-			pc_speaker = audioCtx.createBufferSource();
99
-			var buffer = audioCtx.createBuffer(1, bufferSize * 2, sampleRate);
100
-			pc_speaker.buffer = buffer;
101
-			pc_speaker.connect(audioCtx.destination);
102
-
103
-			var nativeBuffer = emu._malloc(bufferSize);
104
-			var nativeHeap = new Uint8Array(emu.HEAPU8.buffer, nativeBuffer, bufferSize);
105
-
106
-			emu._audio_stream_init(time_ms(), sampleRate);
107
-			emu._audio_stream_set_volume(Math.floor(0.2 * emu._audio_stream_get_max_volume()));
108
-			var startedAt = 0;
109
-			var startedAtMs = 0;
110
-			var lastTime = 0;
111
-
112
-			var write = function(offset) {
113
-				var out = buffer.getChannelData(0);
114
-				var sm = Math.min(time_ms(), ((audioCtx.currentTime - startedAt) * 1000) + startedAtMs);
115
-				emu._audio_stream_generate_u8(sm, nativeBuffer, bufferSize);
116
-				for (var i = 0; i < bufferSize; i++) {
117
-					out[offset + i] = (nativeHeap[i] - 127) / 127.0;
118
-				}
119
-			}
120
-
121
-			var tick = function() {
122
-				var ltPos = Math.floor( (lastTime / (bufferSize / sampleRate)) % 2 );
123
-				var ctPos = Math.floor( (audioCtx.currentTime / (bufferSize / sampleRate)) % 2 );
124
-				if (ltPos != ctPos) {
125
-					write(ltPos * bufferSize);
126
-				}
127
-				lastTime = audioCtx.currentTime;
128
-			};
129
-
130
-			pc_speaker.loop = true;
131
-			startedAt = audioCtx.currentTime;
132
-			startedAtMs = time_ms();
133
-			pc_speaker.start();
134
-			lastTime = startedAt - 0.001;
135
-
136
-			tick();
137
-			setInterval(tick, (bufferSize / sampleRate) * (1000.0 / 2));
138
-		};
139
-
140
-		return {
141
-			on: function(freq) {
142
-				if (audioCtx == undefined) return;
143
-				if (pc_speaker == undefined) init_speaker();
144
-				emu._audio_stream_append_on(time_ms(), freq);
145
-			},
146
-			off: function() {
147
-				if (pc_speaker == undefined) return;
148
-				emu._audio_stream_append_off(time_ms());
149
-			}
150
-		};
151
-	};
152
-
153
-/*	ZetaAudio.createScriptProcessorBased = function(emu) {
154
-		var pc_speaker = undefined;
155
-		var emu = emu;
156
-
157
-		var init_speaker = function() {
158
-			pc_speaker = audioCtx.createScriptProcessor();
159
-
160
-			var bufferSize = pc_speaker.bufferSize;
161
-			var buffer = emu._malloc(bufferSize);
162
-			var heap = new Uint8Array(emu.HEAPU8.buffer, buffer, bufferSize);
163
-
164
-			emu._audio_stream_init(time_ms(), Math.floor(audioCtx.sampleRate));
165
-			emu._audio_stream_set_volume(Math.floor(0.2 * emu._audio_stream_get_max_volume()));
166
-
167
-			pc_speaker.onaudioprocess = function(event) {
168
-				var out = event.outputBuffer.getChannelData(0);
169
-				emu._audio_stream_generate_u8(time_ms() - (bufferSize * 1000 / audioCtx.sampleRate), buffer, bufferSize);
170
-				for (var i = 0; i < bufferSize; i++) {
171
-					out[i] = (heap[i] - 127) / 127.0;
172
-				}
173
-				console.log("audio " + bufferSize);
174
-			};
175
-
176
-			pc_speaker.connect(audioCtx.destination);
177
-		};
178
-
179
-		return {
180
-			on: function(freq) {
181
-				if (audioCtx == undefined) return;
182
-				if (pc_speaker == undefined) init_speaker();
183
-				emu._audio_stream_append_on(time_ms(), freq);
184
-			},
185
-			off: function() {
186
-				if (pc_speaker == undefined) return;
187
-				emu._audio_stream_append_off(time_ms());
188
-			}
189
-		};
190
-	}; */
191
-
192
-})();

+ 0
- 70
web/zeta_preload.js View File

@@ -1,70 +0,0 @@
1
-/*!
2
- * Copyright (c) 2018, 2019 Adrian Siekierka
3
- *
4
- * This file is part of Zeta.
5
- *
6
- * Zeta is free software: you can redistribute it and/or modify
7
- * it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Zeta is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
- */
19
-
20
-Zeta = function(options, callback) {
21
-	var options = options;
22
-	if (!options.render) throw "Missing option: render!";
23
-	if (!options.render.canvas) throw "Missing option: render.canvas!";
24
-	if (!options.path) throw "Missing option: path!";
25
-	if (!options.files) throw "Missing option: files!";
26
-
27
-	var scripts_array = [];
28
-	var script_ldr = function() {
29
-		if (scripts_array.length == 0) {
30
-			callback(zeta_emu_create(options));
31
-		} else {
32
-			var scrSrc = scripts_array.shift();
33
-			var scr = document.createElement("script");
34
-			scr.onload = script_ldr;
35
-			scr.src = scrSrc;
36
-			document.body.appendChild(scr);
37
-		}
38
-	}
39
-
40
-	var canvas = options.render.canvas;
41
-	canvas.tabindex=1;
42
-	var ctx = canvas.getContext('2d', {alpha: false});
43
-
44
-	var imgload = new Image();
45
-	imgload.onload = function() {
46
-		ctx.imageSmoothingEnabled = false;
47
-		ctx.drawImage(imgload,0,0,320,175,(canvas.width - 640)/2,(canvas.height - 350)/2,640,350);
48
-	};
49
-	imgload.src = options.path+"loading.png";
50
-
51
-	if (options.dev) {
52
-		scripts_array = [
53
-			options.path+"uzip.min.js",
54
-			options.path+"zeta_native.js",
55
-			options.path+"zeta_vfs.js",
56
-			options.path+"zeta_render.js",
57
-			options.path+"zeta_audio.js",
58
-			options.path+"zeta_kbdmap.js",
59
-			options.path+"zeta.js"
60
-		];
61
-	} else {
62
-		scripts_array = [
63
-			options.path+"uzip.min.js",
64
-			options.path+"zeta_native.js",
65
-			options.path+"zeta.min.js"
66
-		];
67
-	}
68
-
69
-	script_ldr();
70
-}

+ 0
- 217
web/zeta_render.js View File

@@ -1,217 +0,0 @@
1
-/*!
2
- * Copyright (c) 2018, 2019 Adrian Siekierka
3
- *
4
- * This file is part of Zeta.
5
- *
6
- * Zeta is free software: you can redistribute it and/or modify
7
- * it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Zeta is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
- */
19
-
20
-ZetaRender = {};
21
-ZetaRender.toCanvas = function(canvas, options) {
22
-	var blink_duration = (options && options.blink_duration) || 466;
23
-
24
-	var ctx = canvas.getContext('2d', {alpha: false});
25
-	ctx.imageSmoothingEnabled = false;
26
-
27
-	var video_blink = true;
28
-	if (options && options.hasOwnProperty("blink")) {
29
-		video_blink = options.blink;
30
-	}
31
-	var video_mode = -1;
32
-	var chrBuf = [];
33
-	var drawChrWidth;
34
-	var chrWidth, scrWidth;
35
-	var chrHeight, scrHeight;
36
-	var pw, ph, cw, ch;
37
-	var scale = 1;
38
-
39
-	var asciiFg = null;
40
-	var charset = null;
41
-	var palette = null;
42
-	var rdDirty = false;
43
-
44
-	var charset_override_enabled = (options && options.charset_override) || false;
45
-	var charset_override = null;
46
-	if (charset_override_enabled) {
47
-		var coImg = new Image();
48
-		coImg.src = options.charset_override;
49
-		coImg.onload = function() {
50
-			charset_override = coImg;
51
-			rdDirty = true;
52
-		};
53
-	}
54
-
55
-	var updVideoMode = function(val) {
56
-		if (val != video_mode) {
57
-			chrBuf = [];
58
-			if ((val & 0x02) == 2) {
59
-				scrWidth = 80;
60
-			} else {
61
-				scrWidth = 40;
62
-			}
63
-			scrHeight = 25;
64
-			video_mode = val;
65
-		}
66
-
67
-		drawChrWidth = chrWidth * (80 / scrWidth);
68
-
69
-		pw = scrWidth*drawChrWidth;
70
-		ph = scrHeight*chrHeight;
71
-		cw = canvas.width;
72
-		ch = canvas.height;
73
-		scale = Math.min(Math.floor(cw / pw), Math.floor(ch / ph));
74
-	}
75
-
76
-	var drawChar = function(x, y, chr, col, time) {
77
-		if (video_blink && col >= 0x80) {
78
-			col = col & 0x7F;
79
-
80
-			if ((time % blink_duration) >= (blink_duration / 2)) {
81
-				col = (col >> 4) * 0x11;
82
-			}
83
-		}
84
-
85
-		var buffered = chrBuf[y * 80 + x];
86
-		var bufcmp = (chr << 8) | col;
87
-
88
-		if (buffered == bufcmp) {
89
-			return;
90
-		} else {
91
-			chrBuf[y * 80 + x] = bufcmp;
92
-		}
93
-
94
-		x = x * drawChrWidth;
95
-		y = y * chrHeight;
96
-
97
-		var bg = (col >> 4) & 0x0F;
98
-		var fg = (col & 15);
99
-
100
-		var rw = drawChrWidth;
101
-		var rh = chrHeight;
102
-
103
-		if (scale > 1) {
104
-			rw *= scale;
105
-			rh *= scale;
106
-			x = (x*scale) + ((cw - pw*scale) / 2);
107
-			y = (y*scale) + ((ch - ph*scale) / 2);
108
-		} else {
109
-			x += ((cw - pw) / 2);
110
-			y += ((ch - ph) / 2);
111
-		}
112
-
113
-		ctx.fillStyle = palette[bg];
114
-		ctx.fillRect(x, y, rw, rh);
115
-
116
-		if (bg != fg) {
117
-			ctx.drawImage(asciiFg[fg], (chr & 15) * chrWidth, ((chr >> 4) & 15) * chrHeight, chrWidth, chrHeight, x, y, rw, rh);
118
-		}
119
-	}
120
-
121
-	var updRenderData = function() {
122
-		if (palette == null) {
123
-			return;
124
-		}
125
-
126
-		var srcImg = null;
127
-		if (charset_override_enabled) {
128
-			if (charset_override == null) return;
129
-			else srcImg = charset_override;
130
-		} else {
131
-			if (charset == null) return;
132
-		}
133
-
134
-		asciiFg = [];
135
-
136
-		if (srcImg == null) {
137
-			var charCanvas = document.createElement('canvas');
138
-			charCanvas.width = 128;
139
-			charCanvas.height = 224;
140
-			var charCtx = charCanvas.getContext('2d');
141
-			var charData = charCtx.createImageData(128, 224);
142
-			var data = charData.data;
143
-			var dpos = 0;
144
-
145
-			for (var ch = 0; ch < 256; ch++) {
146
-				var rx = (ch & 0xF) * chrWidth;
147
-				var ry = (ch >> 4) * chrHeight;
148
-				for (var cy = 0; cy < chrHeight; cy++, dpos++) {
149
-					var co = ((ry + cy) * 128) + rx;
150
-					var ctmp = charset[dpos];
151
-					for (var cx = 0; cx < chrWidth; cx++, co++, ctmp = ctmp << 1) {
152
-						var cc = ((ctmp >> 7) & 0x01) * 255;
153
-						data[co * 4 + 0] = cc;
154
-						data[co * 4 + 1] = cc;
155
-						data[co * 4 + 2] = cc;
156
-						data[co * 4 + 3] = cc;
157
-					}
158
-				}
159
-			}
160
-
161
-			charCtx.putImageData(charData, 0, 0);
162
-			srcImg = charCanvas;
163
-		}
164
-
165
-		asciiFg[15] = srcImg;
166
-		for (var i = 0; i < 15; i++) {
167
-			var charCanvas = document.createElement('canvas');
168
-			charCanvas.width = 128;
169
-			charCanvas.height = 224;
170
-			var charCtx = charCanvas.getContext('2d');
171
-			charCtx.globalCompositeOperation = 'copy';
172
-			charCtx.drawImage(srcImg, 0, 0);
173
-			charCtx.globalCompositeOperation = 'source-in';
174
-			charCtx.fillStyle = palette[i];
175
-			charCtx.fillRect(0, 0, 128, 224);
176
-			asciiFg[i] = charCanvas;
177
-		}
178
-
179
-		rdDirty = false;
180
-	};
181
-
182
-	return {
183
-		render: function(heap, mode, time) {
184
-			if (rdDirty) updRenderData();
185
-			updVideoMode(mode);
186
-
187
-			var pos = 0;
188
-
189
-			if (asciiFg != null && palette != null) {
190
-				for (var y = 0; y < 25; y++) {
191
-					for (var x = 0; x < scrWidth; x++, pos+=2) {
192
-						drawChar(x, y, heap[pos], heap[pos+1], time);
193
-					}
194
-				}
195
-			}
196
-		},
197
-
198
-		setCharset: function(width, height, heap) {
199
-			chrWidth = width;
200
-			chrHeight = height;
201
-			charset = heap;
202
-
203
-			rdDirty = true;
204
-		},
205
-
206
-		setPalette: function(heap) {
207
-			palette = new Array();
208
-			for (var i = 0; i < 16; i++) {
209
-				var s = (heap[i] & 0xFFFFFF).toString(16);
210
-				while (s.length < 6) s = "0" + s;
211
-				palette[i] = "#" + s;
212
-			}
213
-
214
-			rdDirty = true;
215
-		}
216
-	};
217
-}

+ 0
- 119
web/zeta_vfs.js View File

@@ -1,119 +0,0 @@
1
-/*!
2
- * Copyright (c) 2018, 2019 Adrian Siekierka
3
- *
4
- * This file is part of Zeta.
5
- *
6
- * Zeta is free software: you can redistribute it and/or modify
7
- * it under the terms of the GNU General Public License as published by
8
- * the Free Software Foundation, either version 3 of the License, or
9
- * (at your option) any later version.
10
- *
11
- * Zeta is distributed in the hope that it will be useful,
12
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
13
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14
- * GNU General Public License for more details.
15
- *
16
- * You should have received a copy of the GNU General Public License
17
- * along with Zeta.  If not, see <http://www.gnu.org/licenses/>.
18
- */
19
-
20
-var ZetaVfs = {};
21
-
22
-ZetaVfs.fromMap = function(inMap, options) {
23
-	var map = {};
24
-	for (var key in inMap) {
25
-		map[key.toUpperCase()] = inMap[key];
26
-	}
27
-	var readOnly = (options && options.readonly) || false;
28
-
29
-	return {
30
-		readonly: function() {
31
-			return readOnly;
32
-		},
33
-		contains: function(key) {
34
-			return map.hasOwnProperty(key.toUpperCase());
35
-		},
36
-		get: function(key) {
37
-			if (!map.hasOwnProperty(key.toUpperCase())) return null;
38
-			return map[key.toUpperCase()].slice(0);
39
-		},
40
-		list: function(filter) {
41
-			var a = [];
42
-
43
-			for (var key in map) {
44
-				if (filter == null || filter(key)) {
45
-					a.push(key);
46
-				}
47
-			}
48
-
49
-			return a;
50
-		},
51
-		set: function(key, value) {
52
-			if (readOnly) return false;
53
-			map[key.toUpperCase()] = value;
54
-			return true;
55
-		}
56
-	};
57
-}
58
-
59
-ZetaVfs.fromZip = function(url, filenameFilter, progressCallback, finishCallback) {
60
-	var xhr = new XMLHttpRequest();
61
-	xhr.open("GET", url, true);
62
-	xhr.responseType = "arraybuffer";
63
-
64
-	xhr.onprogress = function(event) {
65
-		progressCallback(event.loaded / event.total);
66
-	};
67
-
68
-	xhr.onload = function() {
69
-		console.log(this.response);
70
-		progressCallback(1);
71
-
72
-		var files = UZIP.parse(this.response);
73
-		var fileMap = {};
74
-		for (var key in files) {
75
-			if (filenameFilter == null || filenameFilter(key)) {
76
-				fileMap[key] = files[key];
77
-			}
78
-		}
79
-		finishCallback(ZetaVfs.fromMap(fileMap));
80
-	};
81
-
82
-	xhr.send();
83
-}
84
-
85
-ZetaVfs.fromProviders = function(providers) {
86
-	return {
87
-		readonly: function() {
88
-			for (var p = 0; p < providers.length; p++) {
89
-				if (providers[p].readonly()) return true;
90
-			}
91
-			return false;
92
-		},
93
-		contains: function(key) {
94
-			for (var p = 0; p < providers.length; p++) {
95
-				if (providers[p].contains(key)) return true;
96
-			}
97
-			return false;
98
-		},
99
-		get: function(key) {
100
-			for (var p = providers.length - 1; p >= 0; p--) {
101
-				if (providers[p].contains(key)) return providers[p].get(key);
102
-			}
103
-			return null;
104
-		},
105
-		list: function(filter) {
106
-			var data = [];
107
-			for (var p = 0; p < providers.length; p++) {
108
-				data = data.concat(providers[p].list(filter));
109
-			}
110
-			return data.sort();
111
-		},
112
-		set: function(key, value) {
113
-			for (var p = providers.length - 1; p >= 0; p--) {
114
-				if (providers[p].set(key, value)) return true;
115
-			}
116
-			return false;
117
-		}
118
-	};
119
-};

Loading…
Cancel
Save