@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
JavaScript
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