Преглед на файлове

Refactor sample

Abstract methods
master
TheoryOfNekomata преди 4 години
родител
ревизия
45f165e2bd
променени са 12 файла, в които са добавени 395 реда и са изтрити 322 реда
  1. +0
    -1
      docs/example.38f122ec.js.map
  2. +16
    -4
      docs/example.c87d7a16.js
  3. +1
    -0
      docs/example.c87d7a16.js.map
  4. +1
    -1
      docs/index.html
  5. +48
    -0
      example/controllers/Channel.ts
  6. +22
    -0
      example/controllers/Generator.ts
  7. +32
    -0
      example/controllers/Instrument.ts
  8. +14
    -316
      example/index.tsx
  9. +6
    -0
      example/services/SoundGenerator.ts
  10. +157
    -0
      example/services/generators/MidiGenerator.ts
  11. +58
    -0
      example/services/generators/WaveGenerator.ts
  12. +40
    -0
      example/services/keyboardMapping.ts

+ 0
- 1
docs/example.38f122ec.js.map
Файловите разлики са ограничени, защото са твърде много
Целия файл


docs/example.c87d7a16.js
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 0
docs/example.c87d7a16.js.map
Файловите разлики са ограничени, защото са твърде много
Целия файл


+ 1
- 1
docs/index.html Целия файл

@@ -1,2 +1,2 @@
<!DOCTYPE html><html lang="en-PH"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,shrink-to-fit=no"><title>Demo | React Musical Keyboard</title><meta name="description" content="@theoryofnekomata/react-musical-keyboard"><style>:root{--color-channel-0:#f55;--color-channel-1:#ff0;--color-channel-2:#0a0;--color-channel-3:#05a;--color-channel-4:#a0f;--color-channel-5:#a00;--color-channel-6:#a50;--color-channel-7:#fa0;--color-channel-8:#0f0;--color-channel-9:#0aa;--color-channel-10:#0ff;--color-channel-11:#f0a;--color-channel-12:#aa0;--color-channel-13:#550;--color-channel-14:#50a;--color-channel-15:#f5f}html{color:#fff;background-color:#000;font-family:system-ui,sans-serif}body,html{width:100%;height:100%}body{margin:0;display:grid;grid-template-areas:"channel instrument instrument" "background background background" "keyboard keyboard keyboard";grid-template-columns:5rem auto auto;grid-template-rows:3rem auto 15rem}#channel{grid-area:channel}#channel,#instrument{-webkit-appearance:none;border:0;outline:0;background-color:initial;color:inherit;font:inherit;padding:0 1rem}#instrument{grid-area:instrument}#keyboard{grid-area:keyboard;width:100%;position:relative;overflow-x:auto;color:#000;background-color:#fff;cursor:pointer}#keyboard-scroll{width:750%;height:100%;top:0;left:0;position:absolute}@media (min-width:1080px){body{grid-template-rows:5rem auto 5rem}#keyboard-scroll{width:100%}}</style></head><body> <script src="example.38f122ec.js"></script>
<!DOCTYPE html><html lang="en-PH"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1,minimum-scale=1,maximum-scale=1,shrink-to-fit=no"><title>Demo | React Musical Keyboard</title><meta name="description" content="@theoryofnekomata/react-musical-keyboard"><style>:root{--color-channel-0:#f55;--color-channel-1:#ff0;--color-channel-2:#0a0;--color-channel-3:#05a;--color-channel-4:#a0f;--color-channel-5:#a00;--color-channel-6:#a50;--color-channel-7:#fa0;--color-channel-8:#0f0;--color-channel-9:#0aa;--color-channel-10:#0ff;--color-channel-11:#f0a;--color-channel-12:#aa0;--color-channel-13:#550;--color-channel-14:#50a;--color-channel-15:#f5f}html{color:#fff;background-color:#000;font-family:system-ui,sans-serif}body,html{width:100%;height:100%}body{margin:0;display:grid;grid-template-areas:"channel instrument instrument" "background background background" "keyboard keyboard keyboard";grid-template-columns:5rem auto auto;grid-template-rows:3rem auto 15rem}#channel{grid-area:channel}#channel,#instrument{-webkit-appearance:none;border:0;outline:0;background-color:initial;color:inherit;font:inherit;padding:0 1rem}#instrument{grid-area:instrument}#keyboard{grid-area:keyboard;width:100%;position:relative;overflow-x:auto;color:#000;background-color:#fff;cursor:pointer}#keyboard-scroll{width:750%;height:100%;top:0;left:0;position:absolute}@media (min-width:1080px){body{grid-template-rows:5rem auto 5rem}#keyboard-scroll{width:100%}}</style></head><body> <script src="example.c87d7a16.js"></script>
</body></html>

+ 48
- 0
example/controllers/Channel.ts Целия файл

@@ -0,0 +1,48 @@
import * as React from 'react'
import SoundGenerator from '../services/SoundGenerator'

type ChangeProps = {
setChannel(channel: number): void
}

type Change = (props: ChangeProps) => React.ChangeEventHandler<HTMLInputElement>

export const change: Change = ({ setChannel }) => e => {
const { value: rawValue } = e.target
const value = Number(rawValue)
setChannel(value)
}

type KeyChannel = {
key: number,
velocity: number,
channel: number,
}

type KeyChannelCallback = (oldKeys: KeyChannel[]) => KeyChannel[]

type HandleProps = {
setKeyChannels(callback: KeyChannelCallback | KeyChannel[]): void,
generator?: SoundGenerator,
}
type Handle = (props: HandleProps) => (newKeys: KeyChannel[]) => void
export const handle: Handle = ({ setKeyChannels, generator, }) => newKeys => {
setKeyChannels((oldKeys) => {
if (generator! !== undefined) {
const oldKeysKeys = oldKeys.map((k) => k.key)
const newKeysKeys = newKeys.map((k) => k.key)
const keysOff = oldKeys.filter((ok) => !newKeysKeys.includes(ok.key))
const keysOn = newKeys.filter((nk) => !oldKeysKeys.includes(nk.key))

keysOn.forEach((k) => {
generator.noteOn(k.channel, k.key, Math.floor(k.velocity * 127))
})

keysOff.forEach((k) => {
generator.noteOff(k.channel, k.key, Math.floor(k.velocity * 127))
})
}

return newKeys
})
}

+ 22
- 0
example/controllers/Generator.ts Целия файл

@@ -0,0 +1,22 @@
import SoundGenerator from '../services/SoundGenerator'
import MidiGenerator from '../services/generators/MidiGenerator'
import WaveGenerator from '../services/generators/WaveGenerator'

type Load = () => Promise<SoundGenerator>

export const load: Load = async (): Promise<SoundGenerator> => {
const { navigator: maybeNavigator } = window
const navigator = maybeNavigator as Navigator & {
requestMIDIAccess: () => Promise<{ outputs: Map<string, unknown> }>
}

if ('requestMIDIAccess' in navigator) {
const m = await navigator.requestMIDIAccess()
const outputs = Array.from(m.outputs.values()) as MIDIOutput[]
if (outputs.length > 0) {
return new MidiGenerator(outputs[0])
}
}

return new WaveGenerator()
}

+ 32
- 0
example/controllers/Instrument.ts Целия файл

@@ -0,0 +1,32 @@
import * as React from 'react'
import SoundGenerator from '../services/SoundGenerator'

type ChangeProps = {
setInstrument(instrument: number): void
}
type Change = (props: ChangeProps) => React.ChangeEventHandler<HTMLSelectElement>
export const change: Change = ({ setInstrument }) => e => {
const { value: rawValue } = e.target
const value = Number(rawValue)
setInstrument(value)
}

type InitializeProps = {
setInstruments(instruments: string[]): void,
generator: SoundGenerator,
}
type Initialize = (props: InitializeProps) => void
export const initialize: Initialize = ({ setInstruments, generator }) => {
setInstruments(generator.getInstrumentNames())
generator.changeInstrument(0, 0)
}

type ReflectProps = {
generator: SoundGenerator,
channel: number,
instrument: number,
}
type Reflect = (props: ReflectProps) => void
export const reflect: Reflect = ({ generator, channel, instrument, }) => {
generator.changeInstrument(channel, instrument)
}

+ 14
- 316
example/index.tsx Целия файл

@@ -2,226 +2,11 @@ import * as React from 'react'
import ReactDOM from 'react-dom'

import Keyboard, { KeyboardMap } from '../src'

interface SoundGenerator {
changeInstrument(channel: number, patch: number): void,
noteOn(channel: number, key: number, velocity: number): void,
noteOff(channel: number, key: number, velocity: number): void,
getInstrumentNames(): string[],
}

type MIDIMessage = [number, number, number?]

interface MIDIOutput {
send(message: MIDIMessage): void
}

class MidiGenerator implements SoundGenerator {
constructor(private output: MIDIOutput) {
}

noteOn(channel: number, key: number, velocity: number) {
this.output.send([0b10010000 + channel, key, velocity])
}

noteOff(channel: number, key: number, velocity: number) {
this.output.send([0b10000000 + channel, key, velocity])
}

changeInstrument(channel: number, patch: number) {
this.output.send([0b11000000 + channel, patch])
}

getInstrumentNames(): string[] {
return [
'Acoustic Grand Piano',
'Bright Acoustic Piano',
'Electric Grand Piano',
'Honky-tonk Piano',
'Electric Piano 1',
'Electric Piano 2',
'Harpsichord',
'Clavi',
'Celesta',
'Glockenspiel',
'Music Box',
'Vibraphone',
'Marimba',
'Xylophone',
'Tubular Bells',
'Dulcimer',
'Drawbar Organ',
'Percussive Organ',
'Rock Organ',
'Church Organ',
'Reed Organ',
'Accordion',
'Harmonica',
'Tango Accordion',
'Acoustic Guitar (nylon)',
'Acoustic Guitar (steel)',
'Electric Guitar (jazz)',
'Electric Guitar (clean)',
'Electric Guitar (muted)',
'Overdriven Guitar',
'Distortion Guitar',
'Guitar harmonics',
'Acoustic Bass',
'Electric Bass (finger)',
'Electric Bass (pick)',
'Fretless Bass',
'Slap Bass 1',
'Slap Bass 2',
'Synth Bass 1',
'Synth Bass 2',
'Violin',
'Viola',
'Cello',
'Contrabass',
'Tremolo Strings',
'Pizzicato Strings',
'Orchestral Harp',
'Timpani',
'String Ensemble 1',
'String Ensemble 2',
'SynthStrings 1',
'SynthStrings 2',
'Choir Aahs',
'Voice Oohs',
'Synth Voice',
'Orchestra Hit',
'Trumpet',
'Trombone',
'Tuba',
'Muted Trumpet',
'French Horn',
'Brass Section',
'SynthBrass 1',
'SynthBrass 2',
'Soprano Sax',
'Alto Sax',
'Tenor Sax',
'Baritone Sax',
'Oboe',
'English Horn',
'Bassoon',
'Clarinet',
'Piccolo',
'Flute',
'Recorder',
'Pan Flute',
'Blown Bottle',
'Shakuhachi',
'Whistle',
'Ocarina',
'Lead 1 (square)',
'Lead 2 (sawtooth)',
'Lead 3 (calliope)',
'Lead 4 (chiff)',
'Lead 5 (charang)',
'Lead 6 (voice)',
'Lead 7 (fifths)',
'Lead 8 (bass + lead)',
'Pad 1 (new age)',
'Pad 2 (warm)',
'Pad 3 (polysynth)',
'Pad 4 (choir)',
'Pad 5 (bowed)',
'Pad 6 (metallic)',
'Pad 7 (halo)',
'Pad 8 (sweep)',
'FX 1 (rain)',
'FX 2 (soundtrack)',
'FX 3 (crystal)',
'FX 4 (atmosphere)',
'FX 5 (brightness)',
'FX 6 (goblins)',
'FX 7 (echoes)',
'FX 8 (sci-fi)',
'Sitar',
'Banjo',
'Shamisen',
'Koto',
'Kalimba',
'Bag pipe',
'Fiddle',
'Shanai',
'Tinkle Bell',
'Agogo',
'Steel Drums',
'Woodblock',
'Taiko Drum',
'Melodic Tom',
'Synth Drum',
'Reverse Cymbal',
'Guitar Fret Noise',
'Breath Noise',
'Seashore',
'Bird Tweet',
'Telephone Ring',
'Helicopter',
'Applause',
'Gunshot',
]
}
}

class WaveGenerator implements SoundGenerator {
private output: AudioContext
private sounds = 'sine triangle sawtooth square'.split(' ')
private oscillators = new Array(16).fill({})
private channels = new Array(16).fill(0)
private baseFrequency = 440

constructor() {
const tryWindow = window as any
const AudioContext = tryWindow.AudioContext || tryWindow['webkitAudioContext']
this.output = new AudioContext()
}

private getKeyFrequency = (keyNumber: number, baseKeyNumber: number, baseKeyFrequency: number) => (
baseKeyFrequency * Math.pow(
Math.pow(2, 1 / 12),
(keyNumber - baseKeyNumber),
)
)

noteOn(channel: number, key: number, velocity: number) {
if (this.oscillators[channel][key]) {
this.oscillators[channel][key].stop()
delete this.oscillators[channel][key]
}

this.oscillators[channel][key] = this.output.createOscillator()
const gainNode = this.output.createGain()

this.oscillators[channel][key].type = this.sounds[this.channels[channel]]
this.oscillators[channel][key].connect(gainNode)
gainNode.connect(this.output.destination)
gainNode.gain.value = velocity * 0.001

this.oscillators[channel][key].frequency.value = this.getKeyFrequency(key, 69, this.baseFrequency)
this.oscillators[channel][key].start()
}

noteOff(channel: number, key: number, _velocity: number) {
if (this.oscillators[channel][key]) {
try {
this.oscillators[channel][key].stop()
} catch (err) {
}
delete this.oscillators[channel][key]
}
}

changeInstrument(channel: number, patch: number) {
this.channels[channel] = patch
}

getInstrumentNames(): string[] {
return this.sounds
}
}
import * as Channel from './controllers/Channel'
import * as Instrument from './controllers/Instrument'
import * as Generator from './controllers/Generator'
import keyboardMapping from './services/keyboardMapping'
import SoundGenerator from './services/SoundGenerator'

const App = () => {
const [channel, setChannel] = React.useState(0)
@@ -231,67 +16,19 @@ const App = () => {
const generator = React.useRef<SoundGenerator | undefined>(undefined)
const scrollRef = React.useRef<HTMLDivElement>(null)

const handleKeyOn = (newKeys: { key: number; velocity: number; channel: number; id: number }[]) => {
setKeyChannels((oldKeys) => {
const oldKeysKeys = oldKeys.map((k) => k.key)
const newKeysKeys = newKeys.map((k) => k.key)
const keysOff = oldKeys.filter((ok) => !newKeysKeys.includes(ok.key))
const keysOn = newKeys.filter((nk) => !oldKeysKeys.includes(nk.key))

keysOn.forEach((k) => {
if (!generator.current) {
return
}
generator.current.noteOn(k.channel, k.key, Math.floor(k.velocity * 127))
})

keysOff.forEach((k) => {
if (!generator.current) {
return
}
generator.current.noteOff(k.channel, k.key, Math.floor(k.velocity * 127))
})

return newKeys
})
}

const handleChangeInstrument: React.ChangeEventHandler<HTMLSelectElement> = (e) => {
const { value: rawValue } = e.target
const value = Number(rawValue)
setInstrument(value)
}

const handleChangeChannel: React.ChangeEventHandler<HTMLInputElement> = (e) => {
const { value: rawValue } = e.target
const value = Number(rawValue)
setChannel(value)
}

React.useEffect(() => {
if (!generator.current) {
return
}
generator.current.changeInstrument(channel, instrument)
Instrument.reflect({ generator: generator.current, channel, instrument })
}, [channel, instrument])

React.useEffect(() => {
const { navigator: maybeNavigator } = window
const navigator = maybeNavigator as Navigator & {
requestMIDIAccess: () => Promise<{ outputs: Map<string, unknown> }>
}
if ('requestMIDIAccess' in navigator) {
navigator.requestMIDIAccess().then((m) => {
generator.current = new MidiGenerator(Array.from(m.outputs.values())[0] as MIDIOutput)
setInstruments(generator.current!.getInstrumentNames())
generator.current.changeInstrument(0, 0)
Generator
.load()
.then(g => {
Instrument.initialize({ setInstruments, generator: generator.current = g, })
})
} else {
generator.current = new WaveGenerator()
setInstruments(generator.current!.getInstrumentNames())
generator.current.changeInstrument(0, 0)
}

}, [])

React.useEffect(() => {
@@ -308,12 +45,12 @@ const App = () => {
id="channel"
min={0}
max={15}
onChange={handleChangeChannel}
onChange={Channel.change({ setChannel, })}
defaultValue={0}
/>
<select
id="instrument"
onChange={handleChangeInstrument}
onChange={Instrument.change({ setInstrument, })}
defaultValue={0}
>
{Array.isArray(instruments) && instruments.map((name, i) => (
@@ -341,47 +78,8 @@ const App = () => {
>
<KeyboardMap
channel={channel}
onChange={handleKeyOn}
keyboardMapping={{
KeyQ: 60,
Digit2: 61,
KeyW: 62,
Digit3: 63,
KeyE: 64,
KeyR: 65,
Digit5: 66,
KeyT: 67,
Digit6: 68,
KeyY: 69,
Digit7: 70,
KeyU: 71,
KeyI: 72,
Digit9: 73,
KeyO: 74,
Digit0: 75,
KeyP: 76,
BracketLeft: 77,
Equal: 78,
BracketRight: 79,

KeyZ: 48,
KeyS: 49,
KeyX: 50,
KeyD: 51,
KeyC: 52,
KeyV: 53,
KeyG: 54,
KeyB: 55,
KeyH: 56,
KeyN: 57,
KeyJ: 58,
KeyM: 59,
Comma: 60,
KeyL: 61,
Period: 62,
Semicolon: 63,
Slash: 64,
}}
onChange={Channel.handle({ setKeyChannels, generator: generator.current!, })}
keyboardMapping={keyboardMapping}
/>
</Keyboard>
</div>


+ 6
- 0
example/services/SoundGenerator.ts Целия файл

@@ -0,0 +1,6 @@
export default interface SoundGenerator {
changeInstrument(channel: number, patch: number): void,
noteOn(channel: number, key: number, velocity: number): void,
noteOff(channel: number, key: number, velocity: number): void,
getInstrumentNames(): string[],
}

+ 157
- 0
example/services/generators/MidiGenerator.ts Целия файл

@@ -0,0 +1,157 @@
import SoundGenerator from '../SoundGenerator'

type MIDIMessage = [number, number, number?]

interface MIDIOutput {
send(message: MIDIMessage): void
}

export default class MidiGenerator implements SoundGenerator {
constructor(private output: MIDIOutput) {
}

noteOn(channel: number, key: number, velocity: number) {
this.output.send([0b10010000 + channel, key, velocity])
}

noteOff(channel: number, key: number, velocity: number) {
this.output.send([0b10000000 + channel, key, velocity])
}

changeInstrument(channel: number, patch: number) {
this.output.send([0b11000000 + channel, patch])
}

getInstrumentNames(): string[] {
return [
'Acoustic Grand Piano',
'Bright Acoustic Piano',
'Electric Grand Piano',
'Honky-tonk Piano',
'Electric Piano 1',
'Electric Piano 2',
'Harpsichord',
'Clavi',
'Celesta',
'Glockenspiel',
'Music Box',
'Vibraphone',
'Marimba',
'Xylophone',
'Tubular Bells',
'Dulcimer',
'Drawbar Organ',
'Percussive Organ',
'Rock Organ',
'Church Organ',
'Reed Organ',
'Accordion',
'Harmonica',
'Tango Accordion',
'Acoustic Guitar (nylon)',
'Acoustic Guitar (steel)',
'Electric Guitar (jazz)',
'Electric Guitar (clean)',
'Electric Guitar (muted)',
'Overdriven Guitar',
'Distortion Guitar',
'Guitar harmonics',
'Acoustic Bass',
'Electric Bass (finger)',
'Electric Bass (pick)',
'Fretless Bass',
'Slap Bass 1',
'Slap Bass 2',
'Synth Bass 1',
'Synth Bass 2',
'Violin',
'Viola',
'Cello',
'Contrabass',
'Tremolo Strings',
'Pizzicato Strings',
'Orchestral Harp',
'Timpani',
'String Ensemble 1',
'String Ensemble 2',
'SynthStrings 1',
'SynthStrings 2',
'Choir Aahs',
'Voice Oohs',
'Synth Voice',
'Orchestra Hit',
'Trumpet',
'Trombone',
'Tuba',
'Muted Trumpet',
'French Horn',
'Brass Section',
'SynthBrass 1',
'SynthBrass 2',
'Soprano Sax',
'Alto Sax',
'Tenor Sax',
'Baritone Sax',
'Oboe',
'English Horn',
'Bassoon',
'Clarinet',
'Piccolo',
'Flute',
'Recorder',
'Pan Flute',
'Blown Bottle',
'Shakuhachi',
'Whistle',
'Ocarina',
'Lead 1 (square)',
'Lead 2 (sawtooth)',
'Lead 3 (calliope)',
'Lead 4 (chiff)',
'Lead 5 (charang)',
'Lead 6 (voice)',
'Lead 7 (fifths)',
'Lead 8 (bass + lead)',
'Pad 1 (new age)',
'Pad 2 (warm)',
'Pad 3 (polysynth)',
'Pad 4 (choir)',
'Pad 5 (bowed)',
'Pad 6 (metallic)',
'Pad 7 (halo)',
'Pad 8 (sweep)',
'FX 1 (rain)',
'FX 2 (soundtrack)',
'FX 3 (crystal)',
'FX 4 (atmosphere)',
'FX 5 (brightness)',
'FX 6 (goblins)',
'FX 7 (echoes)',
'FX 8 (sci-fi)',
'Sitar',
'Banjo',
'Shamisen',
'Koto',
'Kalimba',
'Bag pipe',
'Fiddle',
'Shanai',
'Tinkle Bell',
'Agogo',
'Steel Drums',
'Woodblock',
'Taiko Drum',
'Melodic Tom',
'Synth Drum',
'Reverse Cymbal',
'Guitar Fret Noise',
'Breath Noise',
'Seashore',
'Bird Tweet',
'Telephone Ring',
'Helicopter',
'Applause',
'Gunshot',
]
}
}

+ 58
- 0
example/services/generators/WaveGenerator.ts Целия файл

@@ -0,0 +1,58 @@
import SoundGenerator from '../SoundGenerator'

export default class WaveGenerator implements SoundGenerator {
private output: AudioContext
private sounds = 'sine triangle sawtooth square'.split(' ')
private oscillators = new Array(16).fill({})
private channels = new Array(16).fill(0)
private baseFrequency = 440

constructor() {
const tryWindow = window as any
const AudioContext = tryWindow.AudioContext || tryWindow['webkitAudioContext']
this.output = new AudioContext()
}

private getKeyFrequency = (keyNumber: number, baseKeyNumber: number, baseKeyFrequency: number) => (
baseKeyFrequency * Math.pow(
Math.pow(2, 1 / 12),
(keyNumber - baseKeyNumber),
)
)

noteOn(channel: number, key: number, velocity: number) {
if (this.oscillators[channel][key]) {
this.oscillators[channel][key].stop()
delete this.oscillators[channel][key]
}

this.oscillators[channel][key] = this.output.createOscillator()
const gainNode = this.output.createGain()

this.oscillators[channel][key].type = this.sounds[this.channels[channel]]
this.oscillators[channel][key].connect(gainNode)
gainNode.connect(this.output.destination)
gainNode.gain.value = velocity * 0.001

this.oscillators[channel][key].frequency.value = this.getKeyFrequency(key, 69, this.baseFrequency)
this.oscillators[channel][key].start()
}

noteOff(channel: number, key: number, _velocity: number) {
if (this.oscillators[channel][key]) {
try {
this.oscillators[channel][key].stop()
} catch (err) {
}
delete this.oscillators[channel][key]
}
}

changeInstrument(channel: number, patch: number) {
this.channels[channel] = patch
}

getInstrumentNames(): string[] {
return this.sounds
}
}

+ 40
- 0
example/services/keyboardMapping.ts Целия файл

@@ -0,0 +1,40 @@
export default {
KeyQ: 60,
Digit2: 61,
KeyW: 62,
Digit3: 63,
KeyE: 64,
KeyR: 65,
Digit5: 66,
KeyT: 67,
Digit6: 68,
KeyY: 69,
Digit7: 70,
KeyU: 71,
KeyI: 72,
Digit9: 73,
KeyO: 74,
Digit0: 75,
KeyP: 76,
BracketLeft: 77,
Equal: 78,
BracketRight: 79,

KeyZ: 48,
KeyS: 49,
KeyX: 50,
KeyD: 51,
KeyC: 52,
KeyV: 53,
KeyG: 54,
KeyB: 55,
KeyH: 56,
KeyN: 57,
KeyJ: 58,
KeyM: 59,
Comma: 60,
KeyL: 61,
Period: 62,
Semicolon: 63,
Slash: 64,
}

Зареждане…
Отказ
Запис