UNPKG

@ng-web-apis/midi

Version:

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

336 lines (297 loc) 9.96 kB
import { filter, map, from, switchMap, fromEvent, of, startWith, 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)) { if (event.data[0]) { event.data[0] += 16; } if (event.data[2]) { 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 WaMidiFrequencyPipe { transform = toFrequency; static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WaMidiFrequencyPipe, deps: [], target: i0.ɵɵFactoryTarget.Pipe }); static ɵpipe = i0.ɵɵngDeclarePipe({ minVersion: "14.0.0", version: "20.3.16", ngImport: i0, type: WaMidiFrequencyPipe, isStandalone: true, name: "frequency" }); } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.16", ngImport: i0, type: WaMidiFrequencyPipe, decorators: [{ type: Pipe, args: [{ name: 'frequency' }] }] }); const WA_SYSEX = new InjectionToken('[WA_SYSEX]', { factory: () => false }); const WA_MIDI_ACCESS = new InjectionToken('[WA_MIDI_ACCESS]', { factory: async () => { const navigatorRef = inject(WA_NAVIGATOR); const sysex = inject(WA_SYSEX); return navigatorRef.requestMIDIAccess ? navigatorRef.requestMIDIAccess({ sysex }) : Promise.reject(new Error('Web MIDI API is not supported')); }, }); const WA_MIDI_INPUT = new InjectionToken('[WA_MIDI_INPUT]'); function getPortsStream(ports) { return from(inject(WA_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([]); }), shareReplay(1)); } const WA_MIDI_INPUTS = new InjectionToken('[WA_MIDI_INPUTS]', { factory: () => getPortsStream('inputs') }); const WA_MIDI_MESSAGES = new InjectionToken('[WA_MIDI_MESSAGES]', { factory: () => from(inject(WA_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()), }); const WA_MIDI_OUTPUT = new InjectionToken('[WA_MIDI_OUTPUT]'); const WA_MIDI_OUTPUTS = new InjectionToken('[WA_MIDI_OUTPUTS]', { factory: () => getPortsStream('outputs') }); const WA_MIDI_SUPPORT = new InjectionToken('[WA_MIDI_SUPPORT]', { factory: () => !!inject(WA_NAVIGATOR).requestMIDIAccess, }); const WA_MIDI_INPUT_QUERY = new InjectionToken('[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]'); /** * 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 { WA_MIDI_ACCESS, WA_MIDI_INPUT, WA_MIDI_INPUTS, WA_MIDI_MESSAGES, WA_MIDI_OUTPUT, WA_MIDI_OUTPUTS, WA_MIDI_SUPPORT, WA_SYSEX, WaMidiFrequencyPipe, 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