diff --git a/README.md b/README.md index 4ba8414..bccaed6 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,23 @@ just call `player.pause()` to pause or `player.load()` to load a new MIDI file. Returns `true` if `destroy()` has been called on the player. Returns `false` otherwise. +### `player.render()` + +Renders the loaded MIDI file to audio data. Returns an array of `Float32Array`s +containing the raw left and right sample data for the entire song. The +`render()` method should only be called once the MIDI file and soundfonts have +been loaded: + +``` +player.on("loaded", function() { + const arrays = player.render() + console.log(arrays) +}) +``` + +Note: the uncompressed sample data can consume a lot of memory, depending on +the length of the song. + ### `player.on('error', (err) => {})` This event fires if a fatal error occurs in the player, including if a MIDI file diff --git a/index.js b/index.js index 28b4dd0..756fa03 100644 --- a/index.js +++ b/index.js @@ -148,6 +148,7 @@ class Timidity extends EventEmitter { this._songPtr = songPtr this._lib._mid_song_start(this._songPtr) debugVerbose('Song and instruments are loaded') + this.emit('loaded') } _getMissingInstruments (songPtr, missingCount) { @@ -258,6 +259,44 @@ class Timidity extends EventEmitter { } } + render () { + const songLength = this._lib._mid_song_get_total_time(this._songPtr) / 1000 + const buffSize = songLength * SAMPLE_RATE + const _array = new Int16Array(BUFFER_SIZE * 2) + const arrays = [new Float32Array(buffSize), new Float32Array(buffSize)] + let sampleCount = -1 + let lastSample = 0 + + while (sampleCount) { + const byteCount = this._lib._mid_song_read_wave( + this._songPtr, + this._bufferPtr, + BUFFER_SIZE * BYTES_PER_SAMPLE + ) + sampleCount = byteCount / BYTES_PER_SAMPLE + + if (byteCount) { + _array.set( + this._lib.HEAP16.subarray(this._bufferPtr / 2, (this._bufferPtr + byteCount) / 2) + ) + + for (let i = 0; i < sampleCount; i++) { + arrays[0][i + lastSample] = _array[i * 2] / 0x7FFF + arrays[1][i + lastSample] = _array[i * 2 + 1] / 0x7FFF + } + lastSample = lastSample + sampleCount + this.emit('progress', lastSample, buffSize) + } + } + + // reset in case the user wants to play + this.seek(0) + this.pause() + this._lib._mid_song_start(this._songPtr) + + return arrays + } + _onAudioProcess (event) { const sampleCount = (this._songPtr && this._playing) ? this._readMidiData()