BasicAudioPlayer.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. /*
  2. Partial IAudioPlayer implementation using the high level "soundfont-player" library.
  3. */
  4. import { MidiInstrument } from "../MusicalScore/VoiceData/Instructions/ClefInstruction";
  5. import { IAudioPlayer } from "../Common/Interfaces/IAudioPlayer";
  6. import { AudioContext as SAudioContext } from "standardized-audio-context";
  7. import * as SoundfontPlayer from "soundfont-player";
  8. import midiNames from "./midiNames";
  9. export class BasicAudioPlayer implements IAudioPlayer<SoundfontPlayer.Player> {
  10. private ac: SAudioContext = new SAudioContext();
  11. // private mainTuningRatio: number = 1.0;
  12. private channelVolumes: number[] = [];
  13. // private activeSamples: Map<number, any> = new Map();
  14. private piano: SoundfontPlayer.Player;
  15. protected memoryLoadedSoundFonts: Map<MidiInstrument, SoundfontPlayer.Player> = new Map();
  16. protected channelToSoundFont: Map<number, number> = new Map();
  17. public SoundfontInstrumentOptions = {}; // e.g. set { from: 'server.com/soundfonts/' } for soundfont fetching url
  18. public async open(uniqueInstruments: number[], numberOfinstruments: number = 16): Promise<void> {
  19. if (this.piano === undefined) {
  20. this.piano = await SoundfontPlayer.instrument(
  21. this.ac as unknown as AudioContext,
  22. midiNames[MidiInstrument.Acoustic_Grand_Piano].toLowerCase() as any,
  23. this.SoundfontInstrumentOptions
  24. );
  25. }
  26. for (let i: number = 0; i < numberOfinstruments; i++) {
  27. this.channelVolumes[i] = 0.8;
  28. }
  29. }
  30. public close(): void {
  31. // _activeSamples.Clear();
  32. }
  33. public tuningChanged(tuningInHz: number): void {
  34. console.warn("BasicAudioPlayer tuningChanged not implemented");
  35. //this.mainTuningRatio = tuningInHz / 440;
  36. }
  37. public playSound(
  38. instrumentChannel: number,
  39. key: number,
  40. volume: number,
  41. lengthInMs: number
  42. ): void {
  43. if (key >= 128) { return; }
  44. const sampleVolume: number = Math.min(
  45. 1,
  46. this.channelVolumes[instrumentChannel] * volume
  47. );
  48. const soundFont: SoundfontPlayer.Player = this.memoryLoadedSoundFonts.get(
  49. this.channelToSoundFont.get(instrumentChannel)
  50. );
  51. soundFont.schedule(0, [
  52. { note: key, duration: lengthInMs / 1000, gain: sampleVolume },
  53. ]);
  54. }
  55. public stopSound(instrumentChannel: number, volume: number): void {
  56. //this.memoryLoadedSoundFonts.get(this.channelToSoundFont.get(instrumentChannel))?.stop(); // abrupt
  57. //console.warn("BasicAudioPlayer stopSound not implemented");
  58. }
  59. public async setSound(
  60. instrumentChannel: number,
  61. soundId: MidiInstrument
  62. ): Promise<void> {
  63. if (this.memoryLoadedSoundFonts.get(soundId) === undefined) {
  64. await this.loadSoundFont(soundId);
  65. }
  66. this.channelToSoundFont.set(instrumentChannel, soundId);
  67. }
  68. public async loadSoundFont(soundId: MidiInstrument): Promise<SoundfontPlayer.Player> {
  69. if (this.memoryLoadedSoundFonts.get(soundId) !== undefined) {
  70. return this.memoryLoadedSoundFonts.get(soundId);
  71. }
  72. let nameOrUrl: any = midiNames[soundId].toLowerCase();
  73. if (soundId === MidiInstrument.Percussion) {
  74. // percussion unfortunately doesn't exist in the original soundfonts
  75. nameOrUrl = "https://paulrosen.github.io/midi-js-soundfonts/FluidR3_GM/percussion-mp3.js";
  76. }
  77. const player: SoundfontPlayer.Player = await SoundfontPlayer.instrument(
  78. this.ac as unknown as AudioContext,
  79. nameOrUrl,
  80. this.SoundfontInstrumentOptions
  81. );
  82. this.memoryLoadedSoundFonts.set(soundId, player);
  83. return player;
  84. }
  85. public setVolume(instrumentChannel: number, volume: number): void {
  86. this.channelVolumes[instrumentChannel] = volume;
  87. }
  88. public setSoundFontFilePath(soundId: MidiInstrument, path: string): void {
  89. // TODO: Remove function, not needed for web. If not used to load different soundfonts from URLs?
  90. }
  91. public playbackHasStopped(): void {
  92. //console.warn("BasicAudioPlayer playbackHasStopped not implemented");
  93. }
  94. public getMemoryLoadedSoundFonts(): SoundfontPlayer.Player[] {
  95. return [...this.memoryLoadedSoundFonts.values()];
  96. }
  97. }