UNPKG

@ng-web-apis/midi

Version:

An Observable based library for the use of Web MIDI API with Angular

397 lines (358 loc) 11.3 kB
import { filter, map, from, switchMap, fromEvent, startWith, of, shareReplay, throwError, merge, share } from 'rxjs'; import * as i0 from '@angular/core'; import { Pipe, InjectionToken, inject } from '@angular/core'; import { WA_NAVIGATOR } from '@ng-web-apis/common'; function between(value, min, max) { return value >= min && value <= max; } /** * Filter MIDI messages to aftertouch changes only */ function aftertouch() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 208, 223))); } /** * Filter MIDI messages by channel * * @param channel number from 0 to 15 */ function filterByChannel(channel) { return (source) => source.pipe(filter(({ data }) => (data[0] ?? 0) % 16 === channel)); } /** * Filter MIDI messages by MIDIInput id * * @param id */ function filterById(id) { return (source) => source.pipe(filter(({ target }) => target.id === id)); } /** * Filter MIDI messages by MIDIInput name * * @param name */ function filterByName(name) { return (source) => source.pipe(filter(({ target }) => target.name === name)); } /** * Filter MIDI messages to main volume changes only */ function mainVolume() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 176, 191) && data[1] === 7)); } /** * Filter MIDI messages to modulation wheel changes only */ function modulationWheel() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 176, 191) && data[1] === 1)); } /** * Filter MIDI messages to notes only * * IMPORTANT: It normalizes noteOff events to noteOn with 0 velocity */ function notes() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 128, 159)), map((event) => { if (between(event.data[0] ?? 0, 128, 143)) { event.data[0] += 16; event.data[2] = 0; } return event; })); } /** * Filter MIDI messages to pan changes only */ function pan() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 176, 191) && data[1] === 10)); } /** * Filter MIDI messages to pitch bend changes only */ function pitchBend() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 224, 239))); } /** * Filter MIDI messages to polyphonic aftertouch changes only */ function polyphonicAftertouch() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 160, 175))); } /** * Filter MIDI messages to program changes only */ function programChange() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 208, 223))); } /** * Filter MIDI messages to sustain pedal changes only */ function sustainPedal() { return (source) => source.pipe(filter(({ data }) => between(data[0] ?? 0, 176, 191) && data[1] === 64)); } /** * Extract MIDI data from event */ function toData() { return (source) => source.pipe(map(({ data }) => data)); } /** * Extract data byte (2nd) from MIDI message * * NOTE: Some status messages do not have 2nd byte, use it when you're certain */ function toDataByte() { return (source) => source.pipe(map(({ data }) => data[1] ?? 0)); } /** * Extract status byte (1st) from MIDI message */ function toStatusByte() { return (source) => source.pipe(map(({ data }) => data[0] ?? 0)); } /** * Extract received time from MIDI event */ function toTimeStamp() { return (source) => source.pipe(map(({ timeStamp }) => timeStamp)); } /** * Extract value byte (3rd) from MIDI message * * NOTE: Some status messages do not have 3rd byte, use it when you're certain */ function toValueByte() { return (source) => source.pipe(map(({ data }) => data[2] ?? 0)); } /** * Convert MIDI notes to frequencies * * @param note MIDI note * @param tuning tuning for middle A (440 by default) */ function toFrequency(note, tuning = 440) { return 2 ** ((note - 69) / 12) * tuning; } class FrequencyPipe { transform(note, tuning) { return toFrequency(note, tuning); } static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FrequencyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "16.2.12", ngImport: i0, type: FrequencyPipe, isStandalone: true, name: "frequency" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "16.2.12", ngImport: i0, type: FrequencyPipe, decorators: [{ type: Pipe, args: [{ standalone: true, name: 'frequency', }] }] }); const WA_SYSEX = new InjectionToken('[WA_SYSEX]', { providedIn: 'root', // eslint-disable-next-line no-restricted-syntax factory: () => false, }); /** * @deprecated: drop in v5.0, use {@link WA_SYSEX} */ const SYSEX = WA_SYSEX; const WA_MIDI_ACCESS = new InjectionToken('[WA_MIDI_ACCESS]', { providedIn: 'root', factory: async () => { const navigatorRef = inject(WA_NAVIGATOR); const sysex = inject(SYSEX); return navigatorRef.requestMIDIAccess ? navigatorRef.requestMIDIAccess({ sysex }) : Promise.reject(new Error('Web MIDI API is not supported')); }, }); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_ACCESS} */ const MIDI_ACCESS = WA_MIDI_ACCESS; const WA_MIDI_INPUT = new InjectionToken('[WA_MIDI_INPUT]'); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_INPUT} */ const MIDI_INPUT = WA_MIDI_INPUT; function getPortsStream(ports) { return from(inject(MIDI_ACCESS).catch(() => null)).pipe(switchMap((access) => { const inputs = []; access?.[ports].forEach((input) => inputs.push(input)); return access ? fromEvent(access, 'statechange').pipe(map(() => inputs), startWith(inputs)) : of([]); }), // eslint-disable-next-line rxjs/no-sharereplay shareReplay(1)); } const WA_MIDI_INPUTS = new InjectionToken('[WA_MIDI_INPUTS]', { factory: () => getPortsStream('inputs'), }); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_INPUTS} */ const MIDI_INPUTS = WA_MIDI_INPUTS; const WA_MIDI_MESSAGES = new InjectionToken('[WA_MIDI_MESSAGES]', { providedIn: 'root', factory: () => from(inject(MIDI_ACCESS).catch((e) => e)).pipe(switchMap((access) => access instanceof Error ? throwError(access) : fromEvent(access, 'statechange').pipe(startWith(null), switchMap(() => { const inputs = []; access.inputs.forEach((input) => inputs.push(input)); return merge(...inputs.map((input) => fromEvent(input, 'midimessage'))); }))), share()), }); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_MESSAGES} */ const MIDI_MESSAGES = WA_MIDI_MESSAGES; const WA_MIDI_OUTPUT = new InjectionToken('[WA_MIDI_OUTPUT]'); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_OUTPUT} */ const MIDI_OUTPUT = WA_MIDI_OUTPUT; const WA_MIDI_OUTPUTS = new InjectionToken('[WA_MIDI_OUTPUTS]', { factory: () => getPortsStream('outputs'), }); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_OUTPUTS} */ const MIDI_OUTPUTS = WA_MIDI_OUTPUTS; const WA_MIDI_SUPPORT = new InjectionToken('[WA_MIDI_SUPPORT]', { factory: () => !!inject(WA_NAVIGATOR).requestMIDIAccess, }); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_SUPPORT} */ const MIDI_SUPPORT = WA_MIDI_SUPPORT; const WA_MIDI_INPUT_QUERY = new InjectionToken('[WA_MIDI_INPUT_QUERY]'); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_INPUT_QUERY} */ const MIDI_INPUT_QUERY = WA_MIDI_INPUT_QUERY; /** * Provide MIDIInput by id * * @param id */ function inputById(id) { return [ { provide: WA_MIDI_INPUT_QUERY, useValue: id, }, { provide: WA_MIDI_INPUT, deps: [WA_MIDI_ACCESS, WA_MIDI_INPUT_QUERY], useFactory: async (midiAccess, id) => midiAccess.then((access) => { let result; access.inputs.forEach((input) => { if (input.id === id) { result = input; } }); return result; }), }, ]; } /** * Provide MIDIInput by name * * @param name */ function inputByName(name) { return [ { provide: WA_MIDI_INPUT_QUERY, useValue: name, }, { provide: WA_MIDI_INPUT, deps: [WA_MIDI_ACCESS, WA_MIDI_INPUT_QUERY], useFactory: async (midiAccess, name) => midiAccess.then((access) => { let result; access.inputs.forEach((input) => { if (input.name === name) { result = input; } }); return result; }), }, ]; } const WA_MIDI_OUTPUT_QUERY = new InjectionToken('[WA_MIDI_OUTPUT_QUERY]'); /** * @deprecated: drop in v5.0, use {@link WA_MIDI_OUTPUT_QUERY} */ const MIDI_OUTPUT_QUERY = WA_MIDI_OUTPUT_QUERY; /** * Provide MIDIOutput by id * * @param id */ function outputById(id) { return [ { provide: WA_MIDI_OUTPUT_QUERY, useValue: id, }, { provide: WA_MIDI_OUTPUT, deps: [WA_MIDI_ACCESS, WA_MIDI_OUTPUT_QUERY], useFactory: async (midiAccess, id) => midiAccess.then((access) => { let result; access.outputs.forEach((output) => { if (output.id === id) { result = output; } }); return result; }), }, ]; } /** * Provide MIDIOutput by name * * @param name */ function outputByName(name) { return [ { provide: WA_MIDI_OUTPUT_QUERY, useValue: name, }, { provide: WA_MIDI_OUTPUT, deps: [WA_MIDI_ACCESS, WA_MIDI_OUTPUT_QUERY], useFactory: async (midiAccess, name) => midiAccess.then((access) => { let result; access.outputs.forEach((output) => { if (output.name === name) { result = output; } }); return result; }), }, ]; } const COEFFICIENT = 2 ** (1 / 12); /** * Convert frequencies to MIDI notes * * @param frequency * @param tuning tuning for middle A (440 by default) */ function toNote(frequency, tuning = 440) { return Math.round(Math.log(frequency / tuning) / Math.log(COEFFICIENT)) + 69; } /** * Generated bundle index. Do not edit. */ export { FrequencyPipe, MIDI_ACCESS, MIDI_INPUT, MIDI_INPUTS, MIDI_MESSAGES, MIDI_OUTPUT, MIDI_OUTPUTS, MIDI_SUPPORT, SYSEX, WA_MIDI_ACCESS, WA_MIDI_INPUT, WA_MIDI_INPUTS, WA_MIDI_MESSAGES, WA_MIDI_OUTPUT, WA_MIDI_OUTPUTS, WA_MIDI_SUPPORT, WA_SYSEX, aftertouch, between, filterByChannel, filterById, filterByName, inputById, inputByName, mainVolume, modulationWheel, notes, outputById, outputByName, pan, pitchBend, polyphonicAftertouch, programChange, sustainPedal, toData, toDataByte, toFrequency, toNote, toStatusByte, toTimeStamp, toValueByte }; //# sourceMappingURL=ng-web-apis-midi.mjs.map