/* Partial IAudioPlayer implementation using the high level "soundfont-player" library. */ import { MidiInstrument } from "../MusicalScore/VoiceData/Instructions/ClefInstruction"; import { IAudioPlayer } from "../Common/Interfaces/IAudioPlayer"; import { AudioContext as SAudioContext } from "standardized-audio-context"; import * as SoundfontPlayer from "soundfont-player"; import midiNames from "./midiNames"; export class BasicAudioPlayer implements IAudioPlayer { private ac: SAudioContext = new SAudioContext(); // private mainTuningRatio: number = 1.0; private channelVolumes: number[] = []; // private activeSamples: Map = new Map(); private piano: SoundfontPlayer.Player; protected memoryLoadedSoundFonts: Map = new Map(); protected channelToSoundFont: Map = new Map(); public SoundfontInstrumentOptions = {}; // e.g. set { from: 'server.com/soundfonts/' } for soundfont fetching url public async open(uniqueInstruments: number[], numberOfinstruments: number = 16): Promise { if (this.piano === undefined) { this.piano = await SoundfontPlayer.instrument( this.ac as unknown as AudioContext, midiNames[MidiInstrument.Acoustic_Grand_Piano].toLowerCase() as any, this.SoundfontInstrumentOptions ); } for (let i: number = 0; i < numberOfinstruments; i++) { this.channelVolumes[i] = 0.8; } } public close(): void { // _activeSamples.Clear(); } public tuningChanged(tuningInHz: number): void { console.warn("BasicAudioPlayer tuningChanged not implemented"); //this.mainTuningRatio = tuningInHz / 440; } public playSound( instrumentChannel: number, key: number, volume: number, lengthInMs: number ): void { if (key >= 128) { return; } const sampleVolume: number = Math.min( 1, this.channelVolumes[instrumentChannel] * volume ); const soundFont: SoundfontPlayer.Player = this.memoryLoadedSoundFonts.get( this.channelToSoundFont.get(instrumentChannel) ); soundFont.schedule(0, [ { note: key, duration: lengthInMs / 1000, gain: sampleVolume }, ]); } public stopSound(instrumentChannel: number, volume: number): void { //this.memoryLoadedSoundFonts.get(this.channelToSoundFont.get(instrumentChannel))?.stop(); // abrupt //console.warn("BasicAudioPlayer stopSound not implemented"); } public async setSound( instrumentChannel: number, soundId: MidiInstrument ): Promise { if (this.memoryLoadedSoundFonts.get(soundId) === undefined) { await this.loadSoundFont(soundId); } this.channelToSoundFont.set(instrumentChannel, soundId); } public async loadSoundFont(soundId: MidiInstrument): Promise { if (this.memoryLoadedSoundFonts.get(soundId) !== undefined) { return this.memoryLoadedSoundFonts.get(soundId); } let nameOrUrl: any = midiNames[soundId].toLowerCase(); if (soundId === MidiInstrument.Percussion) { // percussion unfortunately doesn't exist in the original soundfonts nameOrUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/percussion-mp3.js"; } const player: SoundfontPlayer.Player = await SoundfontPlayer.instrument( this.ac as unknown as AudioContext, nameOrUrl, this.SoundfontInstrumentOptions ); this.memoryLoadedSoundFonts.set(soundId, player); return player; } public setVolume(instrumentChannel: number, volume: number): void { this.channelVolumes[instrumentChannel] = volume; } public setSoundFontFilePath(soundId: MidiInstrument, path: string): void { // TODO: Remove function, not needed for web. If not used to load different soundfonts from URLs? } public playbackHasStopped(): void { //console.warn("BasicAudioPlayer playbackHasStopped not implemented"); } public getMemoryLoadedSoundFonts(): SoundfontPlayer.Player[] { return [...this.memoryLoadedSoundFonts.values()]; } }