UNPKG

js-synthesizer

Version:

Synthesizer library for web-based JS program, using with Web Audio or etc.

870 lines 32.5 kB
import { INVALID_POINTER } from './PointerType'; import { _addFunction, _fs, _module, _removeFunction, bindFunctions, defaultMIDIEventCallback, fluid_sequencer_register_client, fluid_settings_setint, fluid_settings_setnum, fluid_settings_setstr, fluid_synth_error, fluid_synth_sfload, malloc, free, waitForInitialized, } from './WasmManager'; import MIDIEvent from './MIDIEvent'; import Sequencer from './Sequencer'; import SequencerEventData from './SequencerEventData'; import Soundfont from './Soundfont'; function setBoolValueForSettings(settings, name, value) { if (typeof value !== 'undefined') { fluid_settings_setint(settings, name, value ? 1 : 0); } } function setIntValueForSettings(settings, name, value) { if (typeof value !== 'undefined') { fluid_settings_setint(settings, name, value); } } function setNumValueForSettings(settings, name, value) { if (typeof value !== 'undefined') { fluid_settings_setnum(settings, name, value); } } function setStrValueForSettings(settings, name, value) { if (typeof value !== 'undefined') { fluid_settings_setstr(settings, name, value); } } function getActiveVoiceCount(synth) { const actualCount = _module._fluid_synth_get_active_voice_count(synth); if (!actualCount) { return 0; } // FluidSynth may return incorrect value for active voice count, // so check internal data and correct it // check if the structure is not changed // for fluidsynth 2.0.x-2.1.x: // 140 === offset [synth->voice] // 144 === offset [synth->active_voice_count] for // for fluidsynth 2.2.x: // 144 === offset [synth->voice] // 148 === offset [synth->active_voice_count] // first check 2.1.x structure let baseOffsetOfVoice = 140; let offsetOfActiveVoiceCount = (synth + baseOffsetOfVoice + 4) >> 2; let structActiveVoiceCount = _module.HEAPU32[offsetOfActiveVoiceCount]; if (structActiveVoiceCount !== actualCount) { // add 4 for 2.2.x baseOffsetOfVoice += 4; offsetOfActiveVoiceCount = (synth + baseOffsetOfVoice + 4) >> 2; structActiveVoiceCount = _module.HEAPU32[offsetOfActiveVoiceCount]; if (structActiveVoiceCount !== actualCount) { // unknown structure const c = console; c.warn('js-synthesizer: cannot check synthesizer internal data (may be changed)'); return actualCount; } } const voiceList = _module.HEAPU32[(synth + baseOffsetOfVoice) >> 2]; // (voice should not be NULL) if (!voiceList || voiceList >= _module.HEAPU32.byteLength) { // unknown structure const c = console; c.warn('js-synthesizer: cannot check synthesizer internal data (may be changed)'); return actualCount; } // count of internal voice data is restricted to polyphony value const voiceCount = _module._fluid_synth_get_polyphony(synth); let isRunning = false; for (let i = 0; i < voiceCount; ++i) { // auto voice = voiceList[i] const voice = _module.HEAPU32[(voiceList >> 2) + i]; if (!voice) { continue; } // offset [voice->status] const status = _module.HEAPU8[voice + 4]; // 4: FLUID_VOICE_OFF if (status !== 4) { isRunning = true; break; } } if (!isRunning) { if (structActiveVoiceCount !== 0) { const c = console; c.warn('js-synthesizer: Active voice count is not zero, but all voices are off:', structActiveVoiceCount); } _module.HEAPU32[offsetOfActiveVoiceCount] = 0; return 0; } return actualCount; } function makeRandomFileName(type, ext) { return `/${type}-r${Math.random() * 65535}-${Math.random() * 65535}${ext}`; } function makeMIDIEventCallback(synth, cb, param) { return (data, event) => { const t = _module._fluid_midi_event_get_type(event); if (cb(synth, t, new MIDIEvent(event, _module), param)) { return 0; } return _module._fluid_synth_handle_midi_event(data, event); }; } /** Default implementation of ISynthesizer */ export default class Synthesizer { constructor() { bindFunctions(); this._settings = INVALID_POINTER; this._synth = INVALID_POINTER; this._player = INVALID_POINTER; this._playerPlaying = false; this._playerCallbackPtr = null; this._fluidSynthCallback = null; this._buffer = INVALID_POINTER; this._bufferSize = 0; this._numPtr = INVALID_POINTER; this._gain = 0.5 /* Gain */; } /** * Initializes with loaded FluidSynth module. * If using this method, you must call this before all methods/constructors, including `waitForWasmInitialized`. * @param mod loaded libfluidsynth.js instance (typically `const mod = Module` (loaded via script tag) or `const mod = require('libfluidsynth-*.js')` (in Node.js)) */ static initializeWithFluidSynthModule(mod) { bindFunctions(mod); } /** Return the promise object that resolves when WebAssembly has been initialized. */ static waitForWasmInitialized() { return waitForInitialized(); } isInitialized() { return this._synth !== INVALID_POINTER; } /** Return the raw synthesizer instance value (pointer for libfluidsynth). */ getRawSynthesizer() { return this._synth; } createAudioNode(context, frameSize) { const node = context.createScriptProcessor(frameSize, 0, 2); node.addEventListener("audioprocess", (ev) => { this.render(ev.outputBuffer); }); return node; } init(sampleRate, settings) { this.close(); const set = (this._settings = _module._new_fluid_settings()); fluid_settings_setnum(set, "synth.sample-rate", sampleRate); if (settings) { if (typeof settings.initialGain !== "undefined") { this._gain = settings.initialGain; } setBoolValueForSettings(set, "synth.chorus.active", settings.chorusActive); setNumValueForSettings(set, "synth.chorus.depth", settings.chorusDepth); setNumValueForSettings(set, "synth.chorus.level", settings.chorusLevel); setIntValueForSettings(set, "synth.chorus.nr", settings.chorusNr); setNumValueForSettings(set, "synth.chorus.speed", settings.chorusSpeed); setIntValueForSettings(set, "synth.midi-channels", settings.midiChannelCount); setStrValueForSettings(set, "synth.midi-bank-select", settings.midiBankSelect); setIntValueForSettings(set, "synth.min-note-length", settings.minNoteLength); setNumValueForSettings(set, "synth.overflow.age", settings.overflowAge); setNumValueForSettings(set, "synth.overflow.important", settings.overflowImportantValue); if (typeof settings.overflowImportantChannels !== "undefined") { fluid_settings_setstr(set, "synth.overflow.important-channels", settings.overflowImportantChannels.join(",")); } setNumValueForSettings(set, "synth.overflow.percussion", settings.overflowPercussion); setNumValueForSettings(set, "synth.overflow.released", settings.overflowReleased); setNumValueForSettings(set, "synth.overflow.sustained", settings.overflowSustained); setNumValueForSettings(set, "synth.overflow.volume", settings.overflowVolume); setIntValueForSettings(set, "synth.polyphony", settings.polyphony); setBoolValueForSettings(set, "synth.reverb.active", settings.reverbActive); setNumValueForSettings(set, "synth.reverb.damp", settings.reverbDamp); setNumValueForSettings(set, "synth.reverb.level", settings.reverbLevel); setNumValueForSettings(set, "synth.reverb.room-size", settings.reverbRoomSize); setNumValueForSettings(set, "synth.reverb.width", settings.reverbWidth); } fluid_settings_setnum(set, "synth.gain", this._gain); this._synth = _module._new_fluid_synth(this._settings); this._numPtr = malloc(8); } close() { if (this._synth === INVALID_POINTER) { return; } this._closePlayer(); _module._delete_fluid_synth(this._synth); this._synth = INVALID_POINTER; _module._delete_fluid_settings(this._settings); this._settings = INVALID_POINTER; free(this._numPtr); this._numPtr = INVALID_POINTER; } isPlaying() { return (this._synth !== INVALID_POINTER && getActiveVoiceCount(this._synth) > 0); } setInterpolation(value, channel) { this.ensureInitialized(); if (typeof channel === "undefined") { channel = -1; } _module._fluid_synth_set_interp_method(this._synth, channel, value); } getGain() { return this._gain; } setGain(gain) { this.ensureInitialized(); _module._fluid_synth_set_gain(this._synth, gain); this._gain = _module._fluid_synth_get_gain(this._synth); } setChannelType(channel, isDrum) { this.ensureInitialized(); // CHANNEL_TYPE_MELODIC = 0, CHANNEL_TYPE_DRUM = 1 _module._fluid_synth_set_channel_type(this._synth, channel, isDrum ? 1 : 0); } waitForVoicesStopped() { return this.flushFramesAsync(); } loadSFont(bin) { this.ensureInitialized(); const name = makeRandomFileName("sfont", ".sf2"); const ub = new Uint8Array(bin); _fs.writeFile(name, ub); const sfont = fluid_synth_sfload(this._synth, name, 1); _fs.unlink(name); return sfont === -1 ? Promise.reject(new Error(fluid_synth_error(this._synth))) : Promise.resolve(sfont); } unloadSFont(id) { this.ensureInitialized(); this.stopPlayer(); this.flushFramesSync(); _module._fluid_synth_sfunload(this._synth, id, 1); } unloadSFontAsync(id) { // not throw with Promise.reject this.ensureInitialized(); this.stopPlayer(); return this.flushFramesAsync().then(() => { _module._fluid_synth_sfunload(this._synth, id, 1); }); } /** * Returns the `Soundfont` instance for specified SoundFont. * @param sfontId loaded SoundFont id ({@link loadSFont} returns this) * @return `Soundfont` instance or `null` if `sfontId` is not valid or loaded */ getSFontObject(sfontId) { return Soundfont.getSoundfontById(this, sfontId); } getSFontBankOffset(id) { this.ensureInitialized(); return Promise.resolve(_module._fluid_synth_get_bank_offset(this._synth, id)); } setSFontBankOffset(id, offset) { this.ensureInitialized(); _module._fluid_synth_set_bank_offset(this._synth, id, offset); } render(outBuffer) { const frameCount = "numberOfChannels" in outBuffer ? outBuffer.length : outBuffer[0].length; const channels = "numberOfChannels" in outBuffer ? outBuffer.numberOfChannels : outBuffer.length; const sizePerChannel = 4 * frameCount; const totalSize = sizePerChannel * 2; if (this._bufferSize < totalSize) { if (this._buffer !== INVALID_POINTER) { free(this._buffer); } this._buffer = malloc(totalSize); this._bufferSize = totalSize; } const memLeft = this._buffer; const memRight = (this._buffer + sizePerChannel); this.renderRaw(memLeft, memRight, frameCount); const aLeft = new Float32Array(_module.HEAPU8.buffer, memLeft, frameCount); const aRight = channels >= 2 ? new Float32Array(_module.HEAPU8.buffer, memRight, frameCount) : null; if ("numberOfChannels" in outBuffer) { if (outBuffer.copyToChannel) { outBuffer.copyToChannel(aLeft, 0, 0); if (aRight) { outBuffer.copyToChannel(aRight, 1, 0); } } else { // copyToChannel API not exist in Safari AudioBuffer const leftData = outBuffer.getChannelData(0); aLeft.forEach((val, i) => (leftData[i] = val)); if (aRight) { const rightData = outBuffer.getChannelData(1); aRight.forEach((val, i) => (rightData[i] = val)); } } } else { outBuffer[0].set(aLeft); if (aRight) { outBuffer[1].set(aRight); } } // check and update player status this.isPlayerPlaying(); } midiNoteOn(chan, key, vel) { _module._fluid_synth_noteon(this._synth, chan, key, vel); } midiNoteOff(chan, key) { _module._fluid_synth_noteoff(this._synth, chan, key); } midiKeyPressure(chan, key, val) { _module._fluid_synth_key_pressure(this._synth, chan, key, val); } midiControl(chan, ctrl, val) { _module._fluid_synth_cc(this._synth, chan, ctrl, val); } midiProgramChange(chan, prognum) { _module._fluid_synth_program_change(this._synth, chan, prognum); } midiChannelPressure(chan, val) { _module._fluid_synth_channel_pressure(this._synth, chan, val); } midiPitchBend(chan, val) { _module._fluid_synth_pitch_bend(this._synth, chan, val); } midiSysEx(data) { const len = data.byteLength; const mem = malloc(len); _module.HEAPU8.set(data, mem); _module._fluid_synth_sysex(this._synth, mem, len, INVALID_POINTER, INVALID_POINTER, INVALID_POINTER, 0); free(mem); } midiPitchWheelSensitivity(chan, val) { _module._fluid_synth_pitch_wheel_sens(this._synth, chan, val); } midiBankSelect(chan, bank) { _module._fluid_synth_bank_select(this._synth, chan, bank); } midiSFontSelect(chan, sfontId) { _module._fluid_synth_sfont_select(this._synth, chan, sfontId); } midiProgramSelect(chan, sfontId, bank, presetNum) { _module._fluid_synth_program_select(this._synth, chan, sfontId, bank, presetNum); } midiUnsetProgram(chan) { _module._fluid_synth_unset_program(this._synth, chan); } midiProgramReset() { _module._fluid_synth_program_reset(this._synth); } midiSystemReset() { _module._fluid_synth_system_reset(this._synth); } midiAllNotesOff(chan) { _module._fluid_synth_all_notes_off(this._synth, typeof chan === "undefined" ? -1 : chan); } midiAllSoundsOff(chan) { _module._fluid_synth_all_sounds_off(this._synth, typeof chan === "undefined" ? -1 : chan); } midiSetChannelType(chan, isDrum) { // CHANNEL_TYPE_MELODIC = 0 // CHANNEL_TYPE_DRUM = 1 _module._fluid_synth_set_channel_type(this._synth, chan, isDrum ? 1 : 0); } /** * Set reverb parameters to the synthesizer. */ setReverb(roomsize, damping, width, level) { _module._fluid_synth_set_reverb(this._synth, roomsize, damping, width, level); } /** * Set reverb roomsize parameter to the synthesizer. */ setReverbRoomsize(roomsize) { _module._fluid_synth_set_reverb_roomsize(this._synth, roomsize); } /** * Set reverb damping parameter to the synthesizer. */ setReverbDamp(damping) { _module._fluid_synth_set_reverb_damp(this._synth, damping); } /** * Set reverb width parameter to the synthesizer. */ setReverbWidth(width) { _module._fluid_synth_set_reverb_width(this._synth, width); } /** * Set reverb level to the synthesizer. */ setReverbLevel(level) { _module._fluid_synth_set_reverb_level(this._synth, level); } /** * Enable or disable reverb effect of the synthesizer. */ setReverbOn(on) { _module._fluid_synth_set_reverb_on(this._synth, on ? 1 : 0); } /** * Get reverb roomsize parameter of the synthesizer. */ getReverbRoomsize() { return _module._fluid_synth_get_reverb_roomsize(this._synth); } /** * Get reverb damping parameter of the synthesizer. */ getReverbDamp() { return _module._fluid_synth_get_reverb_damp(this._synth); } /** * Get reverb level of the synthesizer. */ getReverbLevel() { return _module._fluid_synth_get_reverb_level(this._synth); } /** * Get reverb width parameter of the synthesizer. */ getReverbWidth() { return _module._fluid_synth_get_reverb_width(this._synth); } /** * Set chorus parameters to the synthesizer. */ setChorus(voiceCount, level, speed, depthMillisec, type) { _module._fluid_synth_set_chorus(this._synth, voiceCount, level, speed, depthMillisec, type); } /** * Set chorus voice count parameter to the synthesizer. */ setChorusVoiceCount(voiceCount) { _module._fluid_synth_set_chorus_nr(this._synth, voiceCount); } /** * Set chorus level parameter to the synthesizer. */ setChorusLevel(level) { _module._fluid_synth_set_chorus_level(this._synth, level); } /** * Set chorus speed parameter to the synthesizer. */ setChorusSpeed(speed) { _module._fluid_synth_set_chorus_speed(this._synth, speed); } /** * Set chorus depth parameter to the synthesizer. */ setChorusDepth(depthMillisec) { _module._fluid_synth_set_chorus_depth(this._synth, depthMillisec); } /** * Set chorus modulation type to the synthesizer. */ setChorusType(type) { _module._fluid_synth_set_chorus_type(this._synth, type); } /** * Enable or disable chorus effect of the synthesizer. */ setChorusOn(on) { _module._fluid_synth_set_chorus_on(this._synth, on ? 1 : 0); } /** * Get chorus voice count of the synthesizer. */ getChorusVoiceCount() { return _module._fluid_synth_get_chorus_nr(this._synth); } /** * Get chorus level of the synthesizer. */ getChorusLevel() { return _module._fluid_synth_get_chorus_level(this._synth); } /** * Get chorus speed of the synthesizer. */ getChorusSpeed() { return _module._fluid_synth_get_chorus_speed(this._synth); } /** * Get chorus depth (in milliseconds) of the synthesizer. */ getChorusDepth() { return _module._fluid_synth_get_chorus_depth(this._synth); } /** * Get chorus modulation type of the synthesizer. */ getChorusType() { return _module._fluid_synth_get_chorus_type(this._synth); } /** * Get generator value assigned to the MIDI channel. * @param channel MIDI channel number * @param param generator ID * @return a value related to the generator */ getGenerator(channel, param) { return _module._fluid_synth_get_gen(this._synth, channel, param); } /** * Set generator value assigned to the MIDI channel. * @param channel MIDI channel number * @param param generator ID * @param value a value related to the generator */ setGenerator(channel, param, value) { _module._fluid_synth_set_gen(this._synth, channel, param, value); } /** * Return the current legato mode of the channel. * @param channel MIDI channel number * @return legato mode */ getLegatoMode(channel) { _module._fluid_synth_get_legato_mode(this._synth, channel, this._numPtr); return _module.HEAP32[this._numPtr >> 2]; } /** * Set the current legato mode of the channel. * @param channel MIDI channel number * @param mode legato mode */ setLegatoMode(channel, mode) { _module._fluid_synth_set_legato_mode(this._synth, channel, mode); } /** * Return the current portamento mode of the channel. * @param channel MIDI channel number * @return portamento mode */ getPortamentoMode(channel) { _module._fluid_synth_get_portamento_mode(this._synth, channel, this._numPtr); return _module.HEAP32[this._numPtr >> 2]; } /** * Set the current portamento mode of the channel. * @param channel MIDI channel number * @param mode portamento mode */ setPortamentoMode(channel, mode) { _module._fluid_synth_set_portamento_mode(this._synth, channel, mode); } /** * Return the current breath mode of the channel. * @param channel MIDI channel number * @return breath mode (BreathFlags) */ getBreathMode(channel) { _module._fluid_synth_get_breath_mode(this._synth, channel, this._numPtr); return _module.HEAP32[this._numPtr >> 2]; } /** * Set the current breath mode of the channel. * @param channel MIDI channel number * @param flags breath mode flags (BreathFlags) */ setBreathMode(channel, flags) { _module._fluid_synth_set_breath_mode(this._synth, channel, flags); } //////////////////////////////////////////////////////////////////////////// resetPlayer() { return new Promise((resolve) => { this._initPlayer(); resolve(); }); } closePlayer() { this._closePlayer(); } /** @internal */ _initPlayer() { this._closePlayer(); const player = _module._new_fluid_player(this._synth); this._player = player; if (player !== INVALID_POINTER) { if (this._fluidSynthCallback === null) { // hacky retrieve 'fluid_synth_handle_midi_event' callback pointer // * 'playback_callback' is filled with 'fluid_synth_handle_midi_event' by default. // * 'playback_userdata' is filled with the synthesizer pointer by default const funcPtr = _module.HEAPU32[(player + 588) >> 2]; // _fluid_player_t::playback_callback const synthPtr = _module.HEAPU32[(player + 592) >> 2]; // _fluid_player_t::playback_userdata if (synthPtr === this._synth) { this._fluidSynthCallback = funcPtr; } } } else { throw new Error("Out of memory"); } } /** @internal */ _closePlayer() { const p = this._player; if (p === INVALID_POINTER) { return; } this.stopPlayer(); _module._delete_fluid_player(p); this._player = INVALID_POINTER; this._playerCallbackPtr = null; } isPlayerPlaying() { if (this._playerPlaying) { const status = _module._fluid_player_get_status(this._player); if (status === 1 /*FLUID_PLAYER_PLAYING*/) { return true; } this.stopPlayer(); } return false; } addSMFDataToPlayer(bin) { this.ensurePlayerInitialized(); const len = bin.byteLength; const mem = malloc(len); _module.HEAPU8.set(new Uint8Array(bin), mem); const r = _module._fluid_player_add_mem(this._player, mem, len); free(mem); return r !== -1 ? Promise.resolve() : Promise.reject(new Error(fluid_synth_error(this._synth))); } playPlayer() { this.ensurePlayerInitialized(); if (this._playerPlaying) { this.stopPlayer(); } if (_module._fluid_player_play(this._player) === -1) { return Promise.reject(new Error(fluid_synth_error(this._synth))); } this._playerPlaying = true; let resolver = () => { }; const p = new Promise((resolve) => { resolver = resolve; }); this._playerDefer = { promise: p, resolve: resolver, }; return Promise.resolve(); } stopPlayer() { const p = this._player; if (p === INVALID_POINTER || !this._playerPlaying) { return; } _module._fluid_player_stop(p); _module._fluid_player_join(p); _module._fluid_synth_all_sounds_off(this._synth, -1); if (this._playerDefer) { this._playerDefer.resolve(); this._playerDefer = void 0; } this._playerPlaying = false; } retrievePlayerCurrentTick() { this.ensurePlayerInitialized(); return Promise.resolve(_module._fluid_player_get_current_tick(this._player)); } retrievePlayerTotalTicks() { this.ensurePlayerInitialized(); return Promise.resolve(_module._fluid_player_get_total_ticks(this._player)); } retrievePlayerBpm() { this.ensurePlayerInitialized(); return Promise.resolve(_module._fluid_player_get_bpm(this._player)); } retrievePlayerMIDITempo() { this.ensurePlayerInitialized(); return Promise.resolve(_module._fluid_player_get_midi_tempo(this._player)); } seekPlayer(ticks) { this.ensurePlayerInitialized(); _module._fluid_player_seek(this._player, ticks); } setPlayerLoop(loopTimes) { this.ensurePlayerInitialized(); _module._fluid_player_set_loop(this._player, loopTimes); } setPlayerTempo(tempoType, tempo) { this.ensurePlayerInitialized(); _module._fluid_player_set_tempo(this._player, tempoType, tempo); } /** * Hooks MIDI events sent by the player. * initPlayer() must be called before calling this method. * @param callback hook callback function, or null to unhook * @param param any additional data passed to the callback */ hookPlayerMIDIEvents(callback, param) { this.ensurePlayerInitialized(); const oldPtr = this._playerCallbackPtr; if (oldPtr === null && callback === null) { return; } const newPtr = // if callback is specified, add function callback !== null ? _addFunction(makeMIDIEventCallback(this, callback, param), "iii") : // if _fluidSynthCallback is filled, set null to use it for reset callback // if not, add function defaultMIDIEventCallback for reset this._fluidSynthCallback !== null ? null : _addFunction(defaultMIDIEventCallback, "iii"); // the third parameter of 'fluid_player_set_playback_callback' should be 'fluid_synth_t*' if (oldPtr !== null && newPtr !== null) { // (using defaultMIDIEventCallback also comes here) _module._fluid_player_set_playback_callback(this._player, newPtr, this._synth); _removeFunction(oldPtr); } else { if (newPtr === null) { // newPtr === null --> use _fluidSynthCallback _module._fluid_player_set_playback_callback(this._player, this._fluidSynthCallback, this._synth); _removeFunction(oldPtr); } else { _module._fluid_player_set_playback_callback(this._player, newPtr, this._synth); } } this._playerCallbackPtr = newPtr; } /** @internal */ ensureInitialized() { if (this._synth === INVALID_POINTER) { throw new Error("Synthesizer is not initialized"); } } /** @internal */ ensurePlayerInitialized() { this.ensureInitialized(); if (this._player === INVALID_POINTER) { this._initPlayer(); } } /** @internal */ renderRaw(memLeft, memRight, frameCount) { _module._fluid_synth_write_float(this._synth, frameCount, memLeft, 0, 1, memRight, 0, 1); } /** @internal */ flushFramesSync() { const frameCount = 65536; const size = 4 * frameCount; const mem = malloc(size * 2); const memLeft = mem; const memRight = (mem + size); while (this.isPlaying()) { this.renderRaw(memLeft, memRight, frameCount); } free(mem); } /** @internal */ flushFramesAsync() { if (!this.isPlaying()) { return Promise.resolve(); } const frameCount = 65536; const size = 4 * frameCount; const mem = malloc(size * 2); const memLeft = mem; const memRight = (mem + size); const nextFrame = typeof setTimeout !== "undefined" ? () => { return new Promise((resolve) => setTimeout(resolve, 0)); } : () => { return Promise.resolve(); }; function head() { return nextFrame().then(tail); } const self = this; function tail() { if (!self.isPlaying()) { free(mem); return Promise.resolve(); } self.renderRaw(memLeft, memRight, frameCount); return head(); } return head(); } waitForPlayerStopped() { return this._playerDefer ? this._playerDefer.promise : Promise.resolve(); } /** * Create the sequencer object for this class. */ static createSequencer() { bindFunctions(); const seq = new Sequencer(); return seq._initialize().then(() => seq); } /** * Registers the user-defined client to the sequencer. * The client can receive events in the time from sequencer process. * @param seq the sequencer instance created by Synthesizer.createSequencer * @param name the client name * @param callback the client callback function that processes event data * @param param additional parameter passed to the callback * @return registered sequencer client id (can be passed to seq.unregisterClient()) */ static registerSequencerClient(seq, name, callback, param) { if (!(seq instanceof Sequencer)) { throw new TypeError("Invalid sequencer instance"); } const ptr = _addFunction((time, ev, _seq, data) => { const e = new SequencerEventData(ev, _module); const type = _module._fluid_event_get_type(ev); callback(time, type, e, seq, data); }, "viiii"); const r = fluid_sequencer_register_client(seq.getRaw(), name, ptr, param); if (r !== -1) { seq._clientFuncMap[r] = ptr; } return r; } /** * Send sequencer event immediately to the specific client. * @param seq the sequencer instance created by Synthesizer.createSequencer * @param clientId registered client id (-1 for registered synthesizer) * @param event event data */ static sendEventToClientNow(seq, clientId, event) { if (!(seq instanceof Sequencer)) { throw new TypeError("Invalid sequencer instance"); } seq.sendEventToClientNow(clientId, event); } /** * (Re-)send event data immediately. * @param seq the sequencer instance created by Synthesizer.createSequencer * @param clientId registered client id (-1 for registered synthesizer) * @param eventData event data which can be retrieved in SequencerClientCallback */ static sendEventNow(seq, clientId, eventData) { if (!(seq instanceof Sequencer)) { throw new TypeError("Invalid sequencer instance"); } seq.sendEventNow(clientId, eventData); } /** * Set interval timer process to call processSequencer for this sequencer. * This method uses 'setInterval' global method to register timer. * @param seq the sequencer instance created by Synthesizer.createSequencer * @param msec time in milliseconds passed to both setInterval and processSequencer * @return return value of 'setInterval' (usually passing to 'clearInterval' will reset event) */ static setIntervalForSequencer(seq, msec) { if (!(seq instanceof Sequencer)) { throw new TypeError("Invalid sequencer instance"); } return seq.setIntervalForSequencer(msec); } } //# sourceMappingURL=Synthesizer.js.map