@@ -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> | </body></html> |
@@ -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 | |||||
}) | |||||
} |
@@ -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() | |||||
} |
@@ -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) | |||||
} |
@@ -2,226 +2,11 @@ import * as React from 'react' | |||||
import ReactDOM from 'react-dom' | import ReactDOM from 'react-dom' | ||||
import Keyboard, { KeyboardMap } from '../src' | 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 App = () => { | ||||
const [channel, setChannel] = React.useState(0) | const [channel, setChannel] = React.useState(0) | ||||
@@ -231,67 +16,19 @@ const App = () => { | |||||
const generator = React.useRef<SoundGenerator | undefined>(undefined) | const generator = React.useRef<SoundGenerator | undefined>(undefined) | ||||
const scrollRef = React.useRef<HTMLDivElement>(null) | 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(() => { | React.useEffect(() => { | ||||
if (!generator.current) { | if (!generator.current) { | ||||
return | return | ||||
} | } | ||||
generator.current.changeInstrument(channel, instrument) | |||||
Instrument.reflect({ generator: generator.current, channel, instrument }) | |||||
}, [channel, instrument]) | }, [channel, instrument]) | ||||
React.useEffect(() => { | 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(() => { | React.useEffect(() => { | ||||
@@ -308,12 +45,12 @@ const App = () => { | |||||
id="channel" | id="channel" | ||||
min={0} | min={0} | ||||
max={15} | max={15} | ||||
onChange={handleChangeChannel} | |||||
onChange={Channel.change({ setChannel, })} | |||||
defaultValue={0} | defaultValue={0} | ||||
/> | /> | ||||
<select | <select | ||||
id="instrument" | id="instrument" | ||||
onChange={handleChangeInstrument} | |||||
onChange={Instrument.change({ setInstrument, })} | |||||
defaultValue={0} | defaultValue={0} | ||||
> | > | ||||
{Array.isArray(instruments) && instruments.map((name, i) => ( | {Array.isArray(instruments) && instruments.map((name, i) => ( | ||||
@@ -341,47 +78,8 @@ const App = () => { | |||||
> | > | ||||
<KeyboardMap | <KeyboardMap | ||||
channel={channel} | 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> | </Keyboard> | ||||
</div> | </div> | ||||
@@ -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[], | |||||
} |
@@ -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', | |||||
] | |||||
} | |||||
} |
@@ -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 | |||||
} | |||||
} |
@@ -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, | |||||
} |