mirror of
https://github.com/whscullin/apple2js.git
synced 2024-01-12 14:14:38 +00:00
Add and that were missing
This commit is contained in:
parent
05b2627b04
commit
3c13c83564
89
js/ui/audio.ts
Normal file
89
js/ui/audio.ts
Normal file
|
@ -0,0 +1,89 @@
|
|||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import Apple2IO from '../apple2io';
|
||||
import { debug } from '../util';
|
||||
|
||||
/*
|
||||
* Audio Handling
|
||||
*/
|
||||
|
||||
const SAMPLE_SIZE = 1024;
|
||||
const SAMPLE_RATE = 44000;
|
||||
|
||||
export default class Audio {
|
||||
private sound = true;
|
||||
private samples: number[][] = [];
|
||||
|
||||
private audioContext;
|
||||
private audioNode;
|
||||
private started = false;
|
||||
|
||||
constructor(io: Apple2IO) {
|
||||
this.audioContext = new AudioContext({
|
||||
sampleRate: SAMPLE_RATE
|
||||
});
|
||||
|
||||
// TODO(flan): MDN says that createScriptProcessor is deprecated and
|
||||
// replaced by AudioWorklet. FF and Chrome support AudioWorklet, but
|
||||
// Safari does not (yet).
|
||||
this.audioNode = this.audioContext.createScriptProcessor(SAMPLE_SIZE, 1, 1);
|
||||
|
||||
this.audioNode.onaudioprocess = (event) => {
|
||||
const data = event.outputBuffer.getChannelData(0);
|
||||
const sample = this.samples.shift();
|
||||
let idx = 0;
|
||||
let len = data.length;
|
||||
|
||||
if (sample) {
|
||||
len = Math.min(sample.length, len);
|
||||
for (; idx < len; idx++) {
|
||||
data[idx] = sample[idx];
|
||||
}
|
||||
}
|
||||
|
||||
for (; idx < data.length; idx++) {
|
||||
data[idx] = 0.0;
|
||||
}
|
||||
};
|
||||
|
||||
this.audioNode.connect(this.audioContext.destination);
|
||||
io.sampleRate(this.audioContext.sampleRate, SAMPLE_SIZE);
|
||||
io.addSampleListener((sample: number[]) => {
|
||||
if (this.sound) {
|
||||
if (this.samples.length < 5) {
|
||||
this.samples.push(sample);
|
||||
}
|
||||
}
|
||||
});
|
||||
debug('Sound initialized');
|
||||
}
|
||||
|
||||
|
||||
autoStart() {
|
||||
if (this.audioContext && !this.started) {
|
||||
this.samples = [];
|
||||
this.audioContext.resume();
|
||||
this.started = true;
|
||||
}
|
||||
}
|
||||
|
||||
start() {
|
||||
if (this.audioContext) {
|
||||
this.samples = [];
|
||||
this.audioContext.resume();
|
||||
}
|
||||
}
|
||||
|
||||
enable(enable: boolean) {
|
||||
this.sound = enable;
|
||||
}
|
||||
}
|
152
js/ui/gamepad.ts
Normal file
152
js/ui/gamepad.ts
Normal file
|
@ -0,0 +1,152 @@
|
|||
/* Copyright 2010-2019 Will Scullin <scullin@scullinsteel.com>
|
||||
*
|
||||
* Permission to use, copy, modify, distribute, and sell this software and its
|
||||
* documentation for any purpose is hereby granted without fee, provided that
|
||||
* the above copyright notice appear in all copies and that both that
|
||||
* copyright notice and this permission notice appear in supporting
|
||||
* documentation. No representations are made about the suitability of this
|
||||
* software for any purpose. It is provided "as is" without express or
|
||||
* implied warranty.
|
||||
*/
|
||||
|
||||
import Apple2IO from '../apple2io';
|
||||
import { KnownKeys } from '../types';
|
||||
|
||||
export let gamepad: Gamepad | null = null;
|
||||
|
||||
const BUTTON = {
|
||||
// Buttons
|
||||
'A': 0,
|
||||
'B': 1,
|
||||
'X': 2,
|
||||
'Y': 3,
|
||||
|
||||
// Triggers
|
||||
'L1': 4,
|
||||
'R1': 5,
|
||||
|
||||
// Analog stick buttons
|
||||
'L3': 6,
|
||||
'R3': 7,
|
||||
|
||||
// Special
|
||||
'START': 8,
|
||||
'SELECT': 9,
|
||||
'LOGO': 10,
|
||||
|
||||
// D pad
|
||||
'UP': 11,
|
||||
'DOWN': 12,
|
||||
'LEFT': 13,
|
||||
'RIGHT': 14
|
||||
} as const;
|
||||
|
||||
type ButtonType = KnownKeys<typeof BUTTON>;
|
||||
|
||||
/**
|
||||
* A `GamepadConfiguration` maps buttons on the controller to Apple Paddle
|
||||
* buttons or keys on the keyboard. If the value is a number, it must be
|
||||
* 0 | 1 | 2 and will map to the corresponding paddle button. If the value
|
||||
* is a string, the _first_ character of the string is used as a key to
|
||||
* press on the keyboard.
|
||||
*/
|
||||
export type GamepadConfiguration = {
|
||||
[K in ButtonType]?: 0 | 1 | 2 | string;
|
||||
};
|
||||
|
||||
const DEFAULT_GAMEPAD: GamepadConfiguration = {
|
||||
'A': 0,
|
||||
'B': 1,
|
||||
'L1': 0,
|
||||
'R1': 1,
|
||||
'START': '\x1B'
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* An array with 16 entries. For each entry _e_:
|
||||
*
|
||||
* * if _e_ <= 0, then _-e_ is 0 | 1 | 2 and represents a joystick button;
|
||||
* * if _e_ > 0, then _e_ is a key on the keyboard that is pressed;
|
||||
* * if _e_ is undefined, nothing happens.
|
||||
*/
|
||||
const gamepadMap: Array<number | undefined> = [];
|
||||
/**
|
||||
* An array with 16 entries saying whether or not the given button is
|
||||
* currently pressed.
|
||||
*/
|
||||
const gamepadState: boolean[] = [];
|
||||
let flipX = false;
|
||||
let flipY = false;
|
||||
|
||||
|
||||
window.addEventListener('gamepadconnected', function (e: GamepadEvent) {
|
||||
gamepad = e.gamepad;
|
||||
});
|
||||
|
||||
export function processGamepad(io: Apple2IO) {
|
||||
// Always use the first gamepad
|
||||
gamepad = navigator.getGamepads()[0];
|
||||
if (!gamepad) {
|
||||
return;
|
||||
}
|
||||
const x = (gamepad.axes[0] * 1.414 + 1) / 2.0;
|
||||
const y = (gamepad.axes[1] * 1.414 + 1) / 2.0;
|
||||
io.paddle(0, flipX ? 1.0 - x : x);
|
||||
io.paddle(1, flipY ? 1.0 - y : y);
|
||||
for (let idx = 0; idx < gamepad.buttons.length; idx++) {
|
||||
const val = gamepadMap[idx];
|
||||
if (val !== undefined) {
|
||||
const old = gamepadState[idx];
|
||||
const button = gamepad.buttons[idx];
|
||||
let pressed: boolean;
|
||||
if (typeof button === 'object') {
|
||||
pressed = button.pressed;
|
||||
} else {
|
||||
pressed = (button === 1.0);
|
||||
}
|
||||
|
||||
if (pressed && !old) {
|
||||
if (val <= 0) {
|
||||
io.buttonDown(-val as 0 | 1 | 2);
|
||||
} else {
|
||||
io.keyDown(gamepadMap[idx]!);
|
||||
}
|
||||
} else if (!pressed && old) {
|
||||
if (val <= 0) {
|
||||
io.buttonUp(-val as 0 | 1 | 2);
|
||||
} else {
|
||||
io.keyUp();
|
||||
}
|
||||
}
|
||||
gamepadState[idx] = pressed;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export function configGamepad(configFlipX: boolean, configFlipY: boolean) {
|
||||
flipX = configFlipX;
|
||||
flipY = configFlipY;
|
||||
}
|
||||
|
||||
export function initGamepad(data?: GamepadConfiguration) {
|
||||
// Clear map
|
||||
for (let idx = 0; idx < 16; idx++) {
|
||||
gamepadMap[idx] = undefined;
|
||||
}
|
||||
const map = data || DEFAULT_GAMEPAD;
|
||||
for (const entry of Object.entries(map)) {
|
||||
const key = entry[0] as ButtonType;
|
||||
const val = entry[1] as number | string;
|
||||
let mapVal;
|
||||
if (typeof val === 'string') {
|
||||
mapVal = val.charCodeAt(0);
|
||||
} else {
|
||||
mapVal = -val;
|
||||
}
|
||||
if (key in BUTTON) {
|
||||
gamepadMap[BUTTON[key]] = mapVal;
|
||||
} else {
|
||||
gamepadMap[parseInt(key, 10)] = mapVal;
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user