Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add save() and saveBlob() methods to SoundFile. Move .wav conversion to helpers.js #315

Merged
merged 2 commits into from
Aug 18, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion src/helpers.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
Expand Down Expand Up @@ -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
};
Expand Down
100 changes: 11 additions & 89 deletions src/soundRecorder.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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
* <a href="/docs/reference/#/p5.SoundFile/saveBlob">`p5.SoundFile.saveBlob`</a>.
*
* @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));
}
}

});
69 changes: 69 additions & 0 deletions src/soundfile.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
* <p>SoundFile object with a path to a file.</p>
Expand Down Expand Up @@ -1640,4 +1641,72 @@ 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 the
* .wav-encoded audio data as a "<a target="_blank" title="Blob reference at
* MDN" href="https://developer.mozilla.org/en-US/docs/Web/API/Blob">Blob</a>".
* A Blob is a file-like data object that can be uploaded to a server
* with an <a href="/docs/reference/#/p5/httpDo">http</a> 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
* <div><code>
*
* function preload() {
* mySound = loadSound('assets/doorbell.mp3');
* }
*
* function setup() {
* 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 `<Audio>` Element accepts Object URL's
* var htmlAudioElt = createAudio(blobUrl).showControls();
*
* createDiv();
*
* // The ObjectURL exists as long as this tab is open
* var input = createInput(blobUrl);
* input.attribute('readonly', true);
* input.mouseClicked(function() { input.elt.select() });
* }
*
* </code></div>
*/
p5.SoundFile.prototype.getBlob = function() {
const dataView = convertToWav(this.buffer);
return new Blob([dataView], { type: 'audio/wav' });
};

});