151 lines
4.7 KiB
TypeScript
151 lines
4.7 KiB
TypeScript
import { pngToRom } from "./pngToRom";
|
|
import { renderCart as rawRenderCart } from "./rawRenderCart";
|
|
|
|
type PicoCart = {
|
|
name: string;
|
|
src: string; // TODO: ideally, accept png data url as well (or even just actual url?)
|
|
}
|
|
|
|
type PlayerButtons = {
|
|
left: boolean;
|
|
right: boolean;
|
|
up: boolean;
|
|
down: boolean;
|
|
o: boolean;
|
|
x: boolean;
|
|
menu: boolean;
|
|
}
|
|
|
|
type PicoPlayerHandle = {
|
|
// external things
|
|
readonly canvas: HTMLCanvasElement;
|
|
|
|
// i/o
|
|
setButtons: (buttons: PlayerButtons[]) => void;
|
|
setMouse: (mouse: {
|
|
x: number;
|
|
y: number;
|
|
leftClick: boolean;
|
|
rightClick: boolean;
|
|
}) => void;
|
|
setGamepadCount: (count: number) => void;
|
|
readonly gpio: number[]; // read + write (should be 256-tuple)
|
|
|
|
// audio
|
|
setAudioContext: (audioContext: AudioContext) => void;
|
|
|
|
// state (all communicated out)
|
|
readonly state: {
|
|
readonly frameNumber: number;
|
|
readonly isPaused: boolean;
|
|
readonly hasFocus: boolean;
|
|
readonly requestPointerLock: boolean;
|
|
readonly requirePageNavigateConfirmation: boolean;
|
|
readonly showDpad: boolean;
|
|
readonly shutdownRequested: boolean;
|
|
readonly soundVolume: number;
|
|
};
|
|
|
|
// misc?
|
|
setTouchDetected: (touchDetected: boolean) => void;
|
|
dropCart: (cart: PicoCart) => void;
|
|
|
|
// Module
|
|
toggleSound: () => void;
|
|
toggleControlMenu: () => void;
|
|
togglePaused: () => void;
|
|
// TODO: rename these two better (what do they do??)
|
|
modDragOver: () => void;
|
|
modDragStop: () => void;
|
|
}
|
|
|
|
const bitfield = (...args: boolean[]): number => {
|
|
if (!args.length) {
|
|
return 0;
|
|
}
|
|
return (args[0]?1:0)+2*bitfield(...args.slice(1));
|
|
}
|
|
|
|
export const makePicoConsole = async (carts: PicoCart[]): Promise<PicoPlayerHandle> => {
|
|
const canvas = document.createElement("canvas");
|
|
canvas.style.imageRendering = "pixelated";
|
|
const Module = {canvas};
|
|
const cartsDatasPromiseArr = carts.map(cart => pngToRom(cart.src));
|
|
const audioContext = new AudioContext();
|
|
try {
|
|
var dummy_source_sfx = audioContext.createBufferSource();
|
|
dummy_source_sfx.buffer = audioContext.createBuffer(1, 1, 22050); // dummy
|
|
dummy_source_sfx.connect(audioContext.destination);
|
|
dummy_source_sfx.start(1, 0.25); // gives InvalidStateError -- why? hasn't been played before
|
|
//dummy_source_sfx.noteOn(0); // deleteme
|
|
}
|
|
catch(err) {
|
|
console.log("** dummy_source_sfx.start(1, 0.25) failed");
|
|
}
|
|
|
|
(window as any).cartsDatasPromiseArr = cartsDatasPromiseArr;
|
|
const cartsDatas = await Promise.all(cartsDatasPromiseArr);
|
|
const handle = rawRenderCart(Module, carts.map(cart => cart.name), cartsDatas, audioContext);
|
|
handle.pico8_state = {};
|
|
handle.pico8_buttons = [0,0,0,0,0,0,0,0];
|
|
handle.pico8_mouse = [0,0,0];
|
|
handle.pico8_gpio = [
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
|
|
];
|
|
handle.pico8_gamepads = {count: 0};
|
|
return {
|
|
canvas,
|
|
state: {
|
|
frameNumber: handle.pico8_state.frame_number!,
|
|
isPaused: !!handle.pico8_state.is_paused!,
|
|
hasFocus: !!handle.pico8_state.has_focus!,
|
|
requestPointerLock: !!handle.pico8_state.request_pointer_lock!,
|
|
requirePageNavigateConfirmation: !!handle.pico8_state.require_page_navigate_confirmation!,
|
|
showDpad: !!handle.pico8_state.show_dpad!,
|
|
shutdownRequested: !!handle.pico8_state.shutdown_requested!,
|
|
soundVolume: handle.pico8_state.sound_volume!,
|
|
},
|
|
gpio: handle.pico8_gpio,
|
|
setMouse({x, y, leftClick, rightClick}) {
|
|
handle.pico8_mouse = [x, y, bitfield(leftClick, rightClick)];
|
|
},
|
|
setButtons(buttons) {
|
|
// TODO: pad this properly here instead of casting
|
|
handle.pico8_buttons = buttons.map(({left, right, up, down, o, x, menu}) => bitfield(left, right, up, down, o, x, menu)) as any;
|
|
},
|
|
setGamepadCount(count) {
|
|
handle.pico8_gamepads = {count};
|
|
},
|
|
setTouchDetected(touchDetected) {
|
|
handle.p8_touch_detected = touchDetected ? 1 : 0;
|
|
},
|
|
dropCart(cart) {
|
|
handle.p8_dropped_cart_name = cart.name;
|
|
// TODO: make sure this is a dataURL first, and if not, load it and then pass it in
|
|
handle.p8_dropped_cart = cart.src;
|
|
},
|
|
setAudioContext(audioContext) {
|
|
handle.pico8_audio_context = audioContext;
|
|
},
|
|
modDragOver: (Module as any).pico8DragOver,
|
|
modDragStop: (Module as any).pico8DragStop,
|
|
togglePaused: (Module as any).pico8TogglePaused,
|
|
toggleSound: (Module as any).pico8ToggleSound,
|
|
toggleControlMenu: (Module as any).pico8ToggleControlMenu,
|
|
}
|
|
} |