La Web MIDI API es una bestia interesante. Aunque ha existido durante casi cinco años, todavía solo es compatible con Chromium. Pero eso no nos impedirá crear un sintetizador completo en Angular. ¡Es hora de llevar la API de audio web al siguiente nivel!
, , , ? 80- — MIDI. , Chrome . , , MIDI-, , . , . , - Angular.
Web MIDI API
API, . MIDI- navigator
Promise
. — — EventTarget
. MIDIMessageEvent
, Uint8Array
. 3 . status byte. , . , , — . MIDI. Angular Observable
, Web MIDI API RxJs.
Dependency Injection
, MIDIAccess
-, . navigator
Promise
, RxJs Observable
. InjectionToken
, NAVIGATOR
@ng-web-apis/common. :
export const MIDI_ACCESS = new InjectionToken<Promise<MIDIAccess>>(
'Promise for MIDIAccess object',
{
factory: () => {
const navigatorRef = inject(NAVIGATOR);
return navigatorRef.requestMIDIAccess
? navigatorRef.requestMIDIAccess()
: Promise.reject(new Error('Web MIDI API is not supported'));
},
},
);
MIDI-. Observable
:
- ,
Observable
, Geolocation API - ,
Promise
Observable
, . :
export const MIDI_MESSAGES = new InjectionToken<Observable<MIDIMessageEvent>>(
'All incoming MIDI messages stream',
{
factory: () =>
from(inject(MIDI_ACCESS).catch((e: Error) => e)).pipe(
switchMap(access =>
access instanceof Error
? throwError(access)
: merge(
...Array.from(access.inputs).map(([_, input]) =>
fromEvent(
input as FromEventTarget<MIDIMessageEvent>,
'midimessage',
),
),
),
),
share(),
),
},
);
- , , MIDIAccess
. :
export function outputById(id: string): Provider[] {
return [
{
provide: MIDI_OUTPUT_QUERY,
useValue: id,
},
{
provide: MIDI_OUTPUT,
deps: [MIDI_ACCESS, MIDI_OUTPUT_QUERY],
useFactory: outputByIdFactory,
},
];
}
export function outputByIdFactory(
midiAccess: Promise<MIDIAccess>,
id: string,
): Promise<MIDIOutput | undefined> {
return midiAccess.then(access => access.outputs.get(id));
}
, ,Provider[]
, ? providers@Directive
, :
providers: [
outputById(‘someId’),
ANOTHER_TOKEN,
SomeService,
]
Angular — .
, .
. , .
:
- . , . , .
- . . , , .
:
export function filterByChannel(
channel: MidiChannel,
): MonoTypeOperatorFunction<MIDIMessageEvent> {
return source => source.pipe(filter(({data}) => data[0] % 16 === channel));
}
Status byte 16: 128—143 (noteOn
) 16 . 144—159 — (noteOff
). , 16 — .
, :
export function notes(): MonoTypeOperatorFunction<MIDIMessageEvent> {
return source =>
source.pipe(
filter(({data}) => between(data[0], 128, 159)),
map(event => {
if (between(event.data[0], 128, 143)) {
event.data[0] += 16;
event.data[2] = 0;
}
return event;
}),
);
}
MIDI-noteOff
-, .noteOn
. ,noteOn
. status byte 16,noteOff
-noteOn
, .
, , :
readonly notes$ = this.messages$.pipe(
catchError(() => EMPTY),
notes(),
toData(),
);
constructor(
@Inject(MIDI_MESSAGES)
private readonly messages$: Observable<MIDIMessageEvent>,
) {}
!
Web Audio API, . , .
. , . scan:
readonly notes$ = this.messages$.pipe(
catchError(() => EMPTY),
notes(),
toData(),
scan(
(map, [_, note, volume]) => map.set(note, volume), new Map()
),
);
, ADSR-. . , ADSR — :
, , , .
@Pipe({
name: 'adsr',
})
export class AdsrPipe implements PipeTransform {
transform(
value: number,
attack: number,
decay: number,
sustain: number,
release: number,
): AudioParamInput {
return value
? [
{
value: 0,
duration: 0,
mode: 'instant',
},
{
value,
duration: attack,
mode: 'linear',
},
{
value: sustain,
duration: decay,
mode: 'linear',
},
]
: {
value: 0,
duration: release,
mode: 'linear',
};
}
}
, , attack. sustain decay. , release.
:
<ng-container
*ngFor="let note of notes | keyvalue; trackBy: noteKey"
>
<ng-container
waOscillatorNode
detune="5"
autoplay
[frequency]="toFrequency(note.key)"
>
<ng-container
waGainNode
gain="0"
[gain]="note.value | adsr: 0:0.1:0.02:1"
>
<ng-container waAudioDestinationNode></ng-container>
</ng-container>
</ng-container>
<ng-container
waOscillatorNode
type="sawtooth"
autoplay
[frequency]="toFrequency(note.key)"
>
<ng-container
waGainNode
gain="0"
[gain]="note.value | adsr: 0:0.1:0.02:1"
>
<ng-container waAudioDestinationNode></ng-container>
<ng-container [waOutput]="convolver"></ng-container>
</ng-container>
</ng-container>
</ng-container>
<ng-container
#convolver="AudioNode"
waConvolverNode
buffer="assets/audio/response.wav"
>
<ng-container waAudioDestinationNode></ng-container>
</ng-container>
keyvalue
, . , . — ConvolverNode
. , , . Chrome:
https://ng-web-apis.github.io/midi
MIDI- — .
, MIDI iframe: https://stackblitz.com/edit/angular-midi
Angular RxJs. Web MIDI API DOM-. MIDI Angular . open-source @ng-web-apis/midi. , Web APIs for Angular. — API Angular . , , Payment Request API Intersection Observer — .
, Angular Web MIDI API — Jamigo.app