Skip to content

Commit dd0779e

Browse files
authored
add save() and getBlob() methods to SoundFile. Move .wav conversion to helpers.js (#315)
* add save and saveBlob to p5.SoundFile. Move convertToWav to helpers.js * saveBlob --> getBlob, update inline example
1 parent feb4bd3 commit dd0779e

File tree

3 files changed

+159
-90
lines changed

3 files changed

+159
-90
lines changed

src/helpers.js

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,7 @@ define(function (require) {
7070
};
7171

7272
// This method converts ANSI notes specified as a string "C4", "Eb3" to a frequency
73-
noteToFreq = function(note) {
73+
var noteToFreq = function(note) {
7474
if (typeof note !== 'string') {
7575
return note;
7676
}
@@ -224,7 +224,85 @@ define(function (require) {
224224
return o;
225225
};
226226

227+
228+
// helper methods to convert audio file as .wav format,
229+
// will use as saving .wav file and saving blob object
230+
// Thank you to Matt Diamond's RecorderJS (MIT License)
231+
// https://github.com/mattdiamond/Recorderjs
232+
function convertToWav(audioBuffer) {
233+
var leftChannel, rightChannel;
234+
leftChannel = audioBuffer.getChannelData(0);
235+
236+
// handle mono files
237+
if (audioBuffer.numberOfChannels > 1) {
238+
rightChannel = audioBuffer.getChannelData(1);
239+
} else {
240+
rightChannel = leftChannel;
241+
}
242+
243+
var interleaved = interleave(leftChannel, rightChannel);
244+
245+
// create the buffer and view to create the .WAV file
246+
var buffer = new window.ArrayBuffer(44 + interleaved.length * 2);
247+
var view = new window.DataView(buffer);
248+
249+
// write the WAV container,
250+
// check spec at: https://web.archive.org/web/20171215131933/http://tiny.systems/software/soundProgrammer/WavFormatDocs.pdf
251+
252+
// RIFF chunk descriptor
253+
writeUTFBytes(view, 0, 'RIFF');
254+
view.setUint32(4, 36 + interleaved.length * 2, true);
255+
writeUTFBytes(view, 8, 'WAVE');
256+
// FMT sub-chunk
257+
writeUTFBytes(view, 12, 'fmt ');
258+
view.setUint32(16, 16, true);
259+
view.setUint16(20, 1, true);
260+
// stereo (2 channels)
261+
view.setUint16(22, 2, true);
262+
view.setUint32(24, 44100, true);
263+
view.setUint32(28, 44100 * 4, true);
264+
view.setUint16(32, 4, true);
265+
view.setUint16(34, 16, true);
266+
// data sub-chunk
267+
writeUTFBytes(view, 36, 'data');
268+
view.setUint32(40, interleaved.length * 2, true);
269+
270+
// write the PCM samples
271+
var lng = interleaved.length;
272+
var index = 44;
273+
var volume = 1;
274+
for (var i = 0; i < lng; i++) {
275+
view.setInt16(index, interleaved[i] * (0x7FFF * volume), true);
276+
index += 2;
277+
}
278+
279+
return view;
280+
}
281+
282+
// helper methods to save waves
283+
function interleave(leftChannel, rightChannel) {
284+
var length = leftChannel.length + rightChannel.length;
285+
var result = new Float32Array(length);
286+
287+
var inputIndex = 0;
288+
289+
for (var index = 0; index < length;) {
290+
result[index++] = leftChannel[inputIndex];
291+
result[index++] = rightChannel[inputIndex];
292+
inputIndex++;
293+
}
294+
return result;
295+
}
296+
297+
function writeUTFBytes(view, offset, string) {
298+
var lng = string.length;
299+
for (var i = 0; i < lng; i++) {
300+
view.setUint8(offset + i, string.charCodeAt(i));
301+
}
302+
}
303+
227304
return {
305+
convertToWav: convertToWav,
228306
midiToFreq: midiToFreq,
229307
noteToFreq: noteToFreq
230308
};

src/soundRecorder.js

Lines changed: 11 additions & 89 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ define(function (require) {
55
// inspiration: recorder.js, Tone.js & typedarray.org
66

77
var p5sound = require('master');
8+
var convertToWav = require('helpers').convertToWav;
89
var ac = p5sound.audiocontext;
910

1011
/**
@@ -244,101 +245,22 @@ define(function (require) {
244245
this._jsNode = null;
245246
};
246247

247-
/**
248-
* Export a usable url for blob object.
249-
*
250-
* @method saveSoundToBlob
251-
* @param {p5.SoundFile} soundFile p5.SoundFile that you wish to export
252-
*/
253-
p5.prototype.saveSoundToBlob = function(soundFile) {
254-
const dataView = convertToWav(soundFile)
255-
const audioBlob = new Blob([dataView], {type: 'audio/wav'})
256-
return URL.createObjectURL(audioBlob)
257-
}
258248

259249
/**
260-
* Save a p5.SoundFile as a .wav audio file.
250+
* Save a p5.SoundFile as a .wav file. The browser will prompt the user
251+
* to download the file to their device.
252+
* For uploading audio to a server, use
253+
* <a href="/docs/reference/#/p5.SoundFile/saveBlob">`p5.SoundFile.saveBlob`</a>.
261254
*
255+
* @for p5
262256
* @method saveSound
263257
* @param {p5.SoundFile} soundFile p5.SoundFile that you wish to save
264-
* @param {String} name name of the resulting .wav file.
258+
* @param {String} fileName name of the resulting .wav file.
265259
*/
266-
p5.prototype.saveSound = function(soundFile, name) {
267-
const dataView = convertToWav(soundFile)
268-
p5.prototype.writeFile( [ dataView ], name, 'wav');
260+
// add to p5.prototype as this is used by the p5 `save()` method.
261+
p5.prototype.saveSound = function (soundFile, fileName) {
262+
const dataView = convertToWav(soundFile.buffer);
263+
p5.prototype.writeFile([dataView], fileName, 'wav');
269264
};
270265

271-
// helper methods to convert audio file as .wav format,
272-
// will use as saving .wav file and saving blob object
273-
function convertToWav(soundFile){
274-
var leftChannel, rightChannel;
275-
leftChannel = soundFile.buffer.getChannelData(0);
276-
277-
// handle mono files
278-
if (soundFile.buffer.numberOfChannels > 1) {
279-
rightChannel = soundFile.buffer.getChannelData(1);
280-
} else {
281-
rightChannel = leftChannel;
282-
}
283-
284-
var interleaved = interleave(leftChannel,rightChannel);
285-
286-
// create the buffer and view to create the .WAV file
287-
var buffer = new window.ArrayBuffer(44 + interleaved.length * 2);
288-
var view = new window.DataView(buffer);
289-
290-
// write the WAV container,
291-
// check spec at: https://ccrma.stanford.edu/courses/422/projects/WaveFormat/
292-
// RIFF chunk descriptor
293-
writeUTFBytes(view, 0, 'RIFF');
294-
view.setUint32(4, 36 + interleaved.length * 2, true);
295-
writeUTFBytes(view, 8, 'WAVE');
296-
// FMT sub-chunk
297-
writeUTFBytes(view, 12, 'fmt ');
298-
view.setUint32(16, 16, true);
299-
view.setUint16(20, 1, true);
300-
// stereo (2 channels)
301-
view.setUint16(22, 2, true);
302-
view.setUint32(24, 44100, true);
303-
view.setUint32(28, 44100 * 4, true);
304-
view.setUint16(32, 4, true);
305-
view.setUint16(34, 16, true);
306-
// data sub-chunk
307-
writeUTFBytes(view, 36, 'data');
308-
view.setUint32(40, interleaved.length * 2, true);
309-
310-
// write the PCM samples
311-
var lng = interleaved.length;
312-
var index = 44;
313-
var volume = 1;
314-
for (var i = 0; i < lng; i++) {
315-
view.setInt16(index, interleaved[i] * (0x7FFF * volume), true);
316-
index += 2;
317-
}
318-
319-
return view
320-
}
321-
322-
// helper methods to save waves
323-
function interleave(leftChannel, rightChannel) {
324-
var length = leftChannel.length + rightChannel.length;
325-
var result = new Float32Array(length);
326-
327-
var inputIndex = 0;
328-
329-
for (var index = 0; index < length; ) {
330-
result[index++] = leftChannel[inputIndex];
331-
result[index++] = rightChannel[inputIndex];
332-
inputIndex++;
333-
}
334-
return result;
335-
}
336-
337-
function writeUTFBytes(view, offset, string) {
338-
var lng = string.length;
339-
for (var i = 0; i < lng; i++) {
340-
view.setUint8(offset + i, string.charCodeAt(i));
341-
}
342-
}
343-
344266
});

src/soundfile.js

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ define(function (require) {
66
var p5sound = require('master');
77
var ac = p5sound.audiocontext;
88
var midiToFreq = require('helpers').midiToFreq;
9+
var convertToWav = require('helpers').convertToWav;
910

1011
/**
1112
* <p>SoundFile object with a path to a file.</p>
@@ -1640,4 +1641,72 @@ define(function (require) {
16401641
this._prevTime = playbackTime;
16411642
};
16421643

1644+
/**
1645+
* Save a p5.SoundFile as a .wav file. The browser will prompt the user
1646+
* to download the file to their device.
1647+
*
1648+
* @method save
1649+
* @param {String} [fileName] name of the resulting .wav file.
1650+
*/
1651+
p5.SoundFile.prototype.save = function(fileName) {
1652+
const dataView = convertToWav(this.buffer);
1653+
p5.prototype.writeFile([dataView], fileName, 'wav');
1654+
};
1655+
1656+
/**
1657+
* This method is useful for sending a SoundFile to a server. It returns the
1658+
* .wav-encoded audio data as a "<a target="_blank" title="Blob reference at
1659+
* MDN" href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>".
1660+
* A Blob is a file-like data object that can be uploaded to a server
1661+
* with an <a href="/docs/reference/#/p5/httpDo">http</a> request. We'll
1662+
* use the `httpDo` options object to send a POST request with some
1663+
* specific options: we encode the request as `multipart/form-data`,
1664+
* and attach the blob as one of the form values using `FormData`.
1665+
*
1666+
*
1667+
* @method getBlob
1668+
* @returns {Blob} A file-like data object
1669+
* @example
1670+
* <div><code>
1671+
*
1672+
* function preload() {
1673+
* mySound = loadSound('assets/doorbell.mp3');
1674+
* }
1675+
*
1676+
* function setup() {
1677+
* noCanvas();
1678+
* var soundBlob = mySound.getBlob();
1679+
*
1680+
* // Now we can send the blob to a server...
1681+
* var serverUrl = 'https://jsonplaceholder.typicode.com/posts';
1682+
* var httpRequestOptions = {
1683+
* method: 'POST',
1684+
* body: new FormData().append('soundBlob', soundBlob),
1685+
* headers: new Headers({
1686+
* 'Content-Type': 'multipart/form-data'
1687+
* })
1688+
* };
1689+
* httpDo(serverUrl, httpRequestOptions);
1690+
*
1691+
* // We can also create an `ObjectURL` pointing to the Blob
1692+
* var blobUrl = URL.createObjectURL(soundBlob);
1693+
*
1694+
* // The `<Audio>` Element accepts Object URL's
1695+
* var htmlAudioElt = createAudio(blobUrl).showControls();
1696+
*
1697+
* createDiv();
1698+
*
1699+
* // The ObjectURL exists as long as this tab is open
1700+
* var input = createInput(blobUrl);
1701+
* input.attribute('readonly', true);
1702+
* input.mouseClicked(function() { input.elt.select() });
1703+
* }
1704+
*
1705+
* </code></div>
1706+
*/
1707+
p5.SoundFile.prototype.getBlob = function() {
1708+
const dataView = convertToWav(this.buffer);
1709+
return new Blob([dataView], { type: 'audio/wav' });
1710+
};
1711+
16431712
});

0 commit comments

Comments
 (0)