From 91451597d68063adcec980c0ac6c2fa1db744724 Mon Sep 17 00:00:00 2001 From: therewasaguy Date: Sat, 11 Aug 2018 17:39:38 -0400 Subject: [PATCH 1/2] add save and saveBlob to p5.SoundFile. Move convertToWav to helpers.js --- src/helpers.js | 80 +++++++++++++++++++++++++++++++++- src/soundRecorder.js | 100 +++++-------------------------------------- src/soundfile.js | 48 +++++++++++++++++++++ 3 files changed, 138 insertions(+), 90 deletions(-) diff --git a/src/helpers.js b/src/helpers.js index a24d7f73..c0297bc2 100644 --- a/src/helpers.js +++ b/src/helpers.js @@ -70,7 +70,7 @@ define(function (require) { }; // This method converts ANSI notes specified as a string "C4", "Eb3" to a frequency - noteToFreq = function(note) { + var noteToFreq = function(note) { if (typeof note !== 'string') { return note; } @@ -224,7 +224,85 @@ define(function (require) { return o; }; + + // helper methods to convert audio file as .wav format, + // will use as saving .wav file and saving blob object + // Thank you to Matt Diamond's RecorderJS (MIT License) + // https://github.com/mattdiamond/Recorderjs + function convertToWav(audioBuffer) { + var leftChannel, rightChannel; + leftChannel = audioBuffer.getChannelData(0); + + // handle mono files + if (audioBuffer.numberOfChannels > 1) { + rightChannel = audioBuffer.getChannelData(1); + } else { + rightChannel = leftChannel; + } + + var interleaved = interleave(leftChannel, rightChannel); + + // create the buffer and view to create the .WAV file + var buffer = new window.ArrayBuffer(44 + interleaved.length * 2); + var view = new window.DataView(buffer); + + // write the WAV container, + // check spec at: https://web.archive.org/web/20171215131933/http://tiny.systems/software/soundProgrammer/WavFormatDocs.pdf + + // RIFF chunk descriptor + writeUTFBytes(view, 0, 'RIFF'); + view.setUint32(4, 36 + interleaved.length * 2, true); + writeUTFBytes(view, 8, 'WAVE'); + // FMT sub-chunk + writeUTFBytes(view, 12, 'fmt '); + view.setUint32(16, 16, true); + view.setUint16(20, 1, true); + // stereo (2 channels) + view.setUint16(22, 2, true); + view.setUint32(24, 44100, true); + view.setUint32(28, 44100 * 4, true); + view.setUint16(32, 4, true); + view.setUint16(34, 16, true); + // data sub-chunk + writeUTFBytes(view, 36, 'data'); + view.setUint32(40, interleaved.length * 2, true); + + // write the PCM samples + var lng = interleaved.length; + var index = 44; + var volume = 1; + for (var i = 0; i < lng; i++) { + view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); + index += 2; + } + + return view; + } + + // helper methods to save waves + function interleave(leftChannel, rightChannel) { + var length = leftChannel.length + rightChannel.length; + var result = new Float32Array(length); + + var inputIndex = 0; + + for (var index = 0; index < length;) { + result[index++] = leftChannel[inputIndex]; + result[index++] = rightChannel[inputIndex]; + inputIndex++; + } + return result; + } + + function writeUTFBytes(view, offset, string) { + var lng = string.length; + for (var i = 0; i < lng; i++) { + view.setUint8(offset + i, string.charCodeAt(i)); + } + } + return { + convertToWav: convertToWav, midiToFreq: midiToFreq, noteToFreq: noteToFreq }; diff --git a/src/soundRecorder.js b/src/soundRecorder.js index 2d1d5e3a..7f2ac563 100644 --- a/src/soundRecorder.js +++ b/src/soundRecorder.js @@ -5,6 +5,7 @@ define(function (require) { // inspiration: recorder.js, Tone.js & typedarray.org var p5sound = require('master'); + var convertToWav = require('helpers').convertToWav; var ac = p5sound.audiocontext; /** @@ -244,101 +245,22 @@ define(function (require) { this._jsNode = null; }; - /** - * Export a usable url for blob object. - * - * @method saveSoundToBlob - * @param {p5.SoundFile} soundFile p5.SoundFile that you wish to export - */ - p5.prototype.saveSoundToBlob = function(soundFile) { - const dataView = convertToWav(soundFile) - const audioBlob = new Blob([dataView], {type: 'audio/wav'}) - return URL.createObjectURL(audioBlob) - } /** - * Save a p5.SoundFile as a .wav audio file. + * Save a p5.SoundFile as a .wav file. The browser will prompt the user + * to download the file to their device. + * For uploading audio to a server, use + * `p5.SoundFile.saveBlob`. * + * @for p5 * @method saveSound * @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save - * @param {String} name name of the resulting .wav file. + * @param {String} fileName name of the resulting .wav file. */ - p5.prototype.saveSound = function(soundFile, name) { - const dataView = convertToWav(soundFile) - p5.prototype.writeFile( [ dataView ], name, 'wav'); + // add to p5.prototype as this is used by the p5 `save()` method. + p5.prototype.saveSound = function (soundFile, fileName) { + const dataView = convertToWav(soundFile.buffer); + p5.prototype.writeFile([dataView], fileName, 'wav'); }; - // helper methods to convert audio file as .wav format, - // will use as saving .wav file and saving blob object - function convertToWav(soundFile){ - var leftChannel, rightChannel; - leftChannel = soundFile.buffer.getChannelData(0); - - // handle mono files - if (soundFile.buffer.numberOfChannels > 1) { - rightChannel = soundFile.buffer.getChannelData(1); - } else { - rightChannel = leftChannel; - } - - var interleaved = interleave(leftChannel,rightChannel); - - // create the buffer and view to create the .WAV file - var buffer = new window.ArrayBuffer(44 + interleaved.length * 2); - var view = new window.DataView(buffer); - - // write the WAV container, - // check spec at: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/ - // RIFF chunk descriptor - writeUTFBytes(view, 0, 'RIFF'); - view.setUint32(4, 36 + interleaved.length * 2, true); - writeUTFBytes(view, 8, 'WAVE'); - // FMT sub-chunk - writeUTFBytes(view, 12, 'fmt '); - view.setUint32(16, 16, true); - view.setUint16(20, 1, true); - // stereo (2 channels) - view.setUint16(22, 2, true); - view.setUint32(24, 44100, true); - view.setUint32(28, 44100 * 4, true); - view.setUint16(32, 4, true); - view.setUint16(34, 16, true); - // data sub-chunk - writeUTFBytes(view, 36, 'data'); - view.setUint32(40, interleaved.length * 2, true); - - // write the PCM samples - var lng = interleaved.length; - var index = 44; - var volume = 1; - for (var i = 0; i < lng; i++) { - view.setInt16(index, interleaved[i] * (0x7FFF * volume), true); - index += 2; - } - - return view - } - - // helper methods to save waves - function interleave(leftChannel, rightChannel) { - var length = leftChannel.length + rightChannel.length; - var result = new Float32Array(length); - - var inputIndex = 0; - - for (var index = 0; index < length; ) { - result[index++] = leftChannel[inputIndex]; - result[index++] = rightChannel[inputIndex]; - inputIndex++; - } - return result; - } - - function writeUTFBytes(view, offset, string) { - var lng = string.length; - for (var i = 0; i < lng; i++) { - view.setUint8(offset + i, string.charCodeAt(i)); - } - } - }); diff --git a/src/soundfile.js b/src/soundfile.js index c36ca3c7..0cc3c760 100644 --- a/src/soundfile.js +++ b/src/soundfile.js @@ -6,6 +6,7 @@ define(function (require) { var p5sound = require('master'); var ac = p5sound.audiocontext; var midiToFreq = require('helpers').midiToFreq; + var convertToWav = require('helpers').convertToWav; /** *

SoundFile object with a path to a file.

@@ -1640,4 +1641,51 @@ define(function (require) { this._prevTime = playbackTime; }; + /** + * Save a p5.SoundFile as a .wav file. The browser will prompt the user + * to download the file to their device. + * + * @method save + * @param {String} [fileName] name of the resulting .wav file. + */ + p5.SoundFile.prototype.save = function(fileName) { + const dataView = convertToWav(this.buffer); + p5.prototype.writeFile([dataView], fileName, 'wav'); + }; + + /** + * This method is useful for sending a SoundFile to a server. It returns a URL to + * the .wav-encoded audio data as a "Blob". A Blob is a + * file-like data object that can be uploaded to a server with an httpPost request. + * + * @method saveBlob + * @returns {ObjectURL} URL to the Blob of audio data. This URL is local to the browser, + * but can be sent to a server with an httpPost + * request. + * @example + *
+ * + * function preload() { + * mySound = loadSound('assets/doorbell.mp3'); + * } + * + * function setup() { + * var dataUrl = mySound.saveBlob(); + + * text("Here's the Blob!", 0, height - 5); + * var input = createInput(dataUrl); + * + * input.attribute('readonly', true); + * input.mouseClicked(function() { input.elt.select() }); + * } + * + *
+ */ + p5.SoundFile.prototype.saveBlob = function() { + const dataView = convertToWav(this.buffer); + const audioBlob = new Blob([dataView], { type: 'audio/wav' }); + return URL.createObjectURL(audioBlob); + } }); From fce9dacc27269ed7d94dd915c5f3e7d1dab100a9 Mon Sep 17 00:00:00 2001 From: therewasaguy Date: Sat, 18 Aug 2018 16:22:27 -0400 Subject: [PATCH 2/2] saveBlob --> getBlob, update inline example --- src/soundfile.js | 59 ++++++++++++++++++++++++++++++++---------------- 1 file changed, 40 insertions(+), 19 deletions(-) diff --git a/src/soundfile.js b/src/soundfile.js index 0cc3c760..d77f17f4 100644 --- a/src/soundfile.js +++ b/src/soundfile.js @@ -1654,16 +1654,18 @@ define(function (require) { }; /** - * This method is useful for sending a SoundFile to a server. It returns a URL to - * the .wav-encoded audio data as a "Blob". A Blob is a - * file-like data object that can be uploaded to a server with an httpPost request. - * - * @method saveBlob - * @returns {ObjectURL} URL to the Blob of audio data. This URL is local to the browser, - * but can be sent to a server with an httpPost - * request. + * This method is useful for sending a SoundFile to a server. It returns the + * .wav-encoded audio data as a "Blob". + * A Blob is a file-like data object that can be uploaded to a server + * with an http request. We'll + * use the `httpDo` options object to send a POST request with some + * specific options: we encode the request as `multipart/form-data`, + * and attach the blob as one of the form values using `FormData`. + * + * + * @method getBlob + * @returns {Blob} A file-like data object * @example *
* @@ -1672,20 +1674,39 @@ define(function (require) { * } * * function setup() { - * var dataUrl = mySound.saveBlob(); - - * text("Here's the Blob!", 0, height - 5); - * var input = createInput(dataUrl); - * + * noCanvas(); + * var soundBlob = mySound.getBlob(); + * + * // Now we can send the blob to a server... + * var serverUrl = 'https://jsonplaceholder.typicode.com/posts'; + * var httpRequestOptions = { + * method: 'POST', + * body: new FormData().append('soundBlob', soundBlob), + * headers: new Headers({ + * 'Content-Type': 'multipart/form-data' + * }) + * }; + * httpDo(serverUrl, httpRequestOptions); + * + * // We can also create an `ObjectURL` pointing to the Blob + * var blobUrl = URL.createObjectURL(soundBlob); + * + * // The `
*/ - p5.SoundFile.prototype.saveBlob = function() { + p5.SoundFile.prototype.getBlob = function() { const dataView = convertToWav(this.buffer); - const audioBlob = new Blob([dataView], { type: 'audio/wav' }); - return URL.createObjectURL(audioBlob); - } + return new Blob([dataView], { type: 'audio/wav' }); + }; + });