mirror of
https://github.com/scratchfoundation/scratch-audio.git
synced 2024-12-22 22:12:48 -05:00
Merge pull request #39 from rschamp/eslint-config-scratch
Use eslint-config-scratch
This commit is contained in:
commit
5007c43ddf
19 changed files with 335 additions and 349 deletions
22
.eslintrc
22
.eslintrc
|
@ -1,22 +0,0 @@
|
||||||
{
|
|
||||||
"parser": "babel-eslint",
|
|
||||||
"rules": {
|
|
||||||
"curly": [2, "multi-line"],
|
|
||||||
"eol-last": [2],
|
|
||||||
"indent": [2, 4],
|
|
||||||
"linebreak-style": [2, "unix"],
|
|
||||||
"max-len": [2, 120, 4],
|
|
||||||
"no-trailing-spaces": [2, { "skipBlankLines": true }],
|
|
||||||
"no-unused-vars": [2, {"args": "after-used", "varsIgnorePattern": "^_"}],
|
|
||||||
"quotes": [2, "single"],
|
|
||||||
"semi": [2, "always"],
|
|
||||||
"space-before-function-paren": [2, "always"],
|
|
||||||
"strict": [2, "never"]
|
|
||||||
},
|
|
||||||
"env": {
|
|
||||||
"browser": true,
|
|
||||||
"es6": true,
|
|
||||||
"node": true
|
|
||||||
},
|
|
||||||
"extends": ["eslint:recommended"]
|
|
||||||
}
|
|
3
.eslintrc.js
Normal file
3
.eslintrc.js
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
module.exports = {
|
||||||
|
extends: ['scratch', 'scratch/node']
|
||||||
|
};
|
|
@ -26,6 +26,7 @@
|
||||||
"babel-loader": "^6.4.1",
|
"babel-loader": "^6.4.1",
|
||||||
"babel-preset-es2015": "^6.24.1",
|
"babel-preset-es2015": "^6.24.1",
|
||||||
"eslint": "^3.19.0",
|
"eslint": "^3.19.0",
|
||||||
|
"eslint-config-scratch": "^3.1.0",
|
||||||
"json": "^9.0.6",
|
"json": "^9.0.6",
|
||||||
"minilog": "^3.0.1",
|
"minilog": "^3.0.1",
|
||||||
"soundfont-player": "0.10.5",
|
"soundfont-player": "0.10.5",
|
||||||
|
|
5
src/.eslintrc.js
Normal file
5
src/.eslintrc.js
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
module.exports = {
|
||||||
|
root: true,
|
||||||
|
extends: ['scratch', 'scratch/es6'],
|
||||||
|
env: {browser: true}
|
||||||
|
};
|
|
@ -1,6 +1,6 @@
|
||||||
var ArrayBufferStream = require('./ArrayBufferStream');
|
const ArrayBufferStream = require('./ArrayBufferStream');
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
var log = require('./log');
|
const log = require('./log');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode wav audio files that have been compressed with the ADPCM format.
|
* Decode wav audio files that have been compressed with the ADPCM format.
|
||||||
|
@ -10,38 +10,37 @@ var log = require('./log');
|
||||||
* https://github.com/LLK/scratch-flash/blob/master/src/sound/WAVFile.as
|
* https://github.com/LLK/scratch-flash/blob/master/src/sound/WAVFile.as
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ADPCMSoundDecoder () {
|
const ADPCMSoundDecoder = function () {};
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decode an ADPCM sound stored in an ArrayBuffer and return a promise
|
* Decode an ADPCM sound stored in an ArrayBuffer and return a promise
|
||||||
* with the decoded audio buffer.
|
* with the decoded audio buffer.
|
||||||
* @param {ArrayBuffer} audioData - containing ADPCM encoded wav audio
|
* @param {ArrayBuffer} audioData - containing ADPCM encoded wav audio
|
||||||
* @return {Tone.Buffer}
|
* @return {Tone.Buffer} the decoded audio buffer
|
||||||
*/
|
*/
|
||||||
ADPCMSoundDecoder.prototype.decode = function (audioData) {
|
ADPCMSoundDecoder.prototype.decode = function (audioData) {
|
||||||
|
|
||||||
return new Promise(function (resolve, reject) {
|
return new Promise((resolve, reject) => {
|
||||||
var stream = new ArrayBufferStream(audioData);
|
const stream = new ArrayBufferStream(audioData);
|
||||||
|
|
||||||
var riffStr = stream.readUint8String(4);
|
const riffStr = stream.readUint8String(4);
|
||||||
if (riffStr != 'RIFF') {
|
if (riffStr !== 'RIFF') {
|
||||||
log.warn('incorrect adpcm wav header');
|
log.warn('incorrect adpcm wav header');
|
||||||
reject();
|
reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
var lengthInHeader = stream.readInt32();
|
const lengthInHeader = stream.readInt32();
|
||||||
if ((lengthInHeader + 8) != audioData.byteLength) {
|
if ((lengthInHeader + 8) !== audioData.byteLength) {
|
||||||
log.warn('adpcm wav length in header: ' + lengthInHeader + ' is incorrect');
|
log.warn(`adpcm wav length in header: ${lengthInHeader} is incorrect`);
|
||||||
}
|
}
|
||||||
|
|
||||||
var wavStr = stream.readUint8String(4);
|
const wavStr = stream.readUint8String(4);
|
||||||
if (wavStr != 'WAVE') {
|
if (wavStr !== 'WAVE') {
|
||||||
log.warn('incorrect adpcm wav header');
|
log.warn('incorrect adpcm wav header');
|
||||||
reject();
|
reject();
|
||||||
}
|
}
|
||||||
|
|
||||||
var formatChunk = this.extractChunk('fmt ', stream);
|
const formatChunk = this.extractChunk('fmt ', stream);
|
||||||
this.encoding = formatChunk.readUint16();
|
this.encoding = formatChunk.readUint16();
|
||||||
this.channels = formatChunk.readUint16();
|
this.channels = formatChunk.readUint16();
|
||||||
this.samplesPerSecond = formatChunk.readUint32();
|
this.samplesPerSecond = formatChunk.readUint32();
|
||||||
|
@ -52,18 +51,18 @@ ADPCMSoundDecoder.prototype.decode = function (audioData) {
|
||||||
this.samplesPerBlock = formatChunk.readUint16();
|
this.samplesPerBlock = formatChunk.readUint16();
|
||||||
this.adpcmBlockSize = ((this.samplesPerBlock - 1) / 2) + 4; // block size in bytes
|
this.adpcmBlockSize = ((this.samplesPerBlock - 1) / 2) + 4; // block size in bytes
|
||||||
|
|
||||||
var samples = this.imaDecompress(this.extractChunk('data', stream), this.adpcmBlockSize);
|
const samples = this.imaDecompress(this.extractChunk('data', stream), this.adpcmBlockSize);
|
||||||
|
|
||||||
// todo: this line is the only place Tone is used here, should be possible to remove
|
// @todo this line is the only place Tone is used here, should be possible to remove
|
||||||
var buffer = Tone.context.createBuffer(1, samples.length, this.samplesPerSecond);
|
const buffer = Tone.context.createBuffer(1, samples.length, this.samplesPerSecond);
|
||||||
|
|
||||||
// todo: optimize this? e.g. replace the divide by storing 1/32768 and multiply?
|
// @todo optimize this? e.g. replace the divide by storing 1/32768 and multiply?
|
||||||
for (var i=0; i<samples.length; i++) {
|
for (let i = 0; i < samples.length; i++) {
|
||||||
buffer.getChannelData(0)[i] = samples[i] / 32768;
|
buffer.getChannelData(0)[i] = samples[i] / 32768;
|
||||||
}
|
}
|
||||||
|
|
||||||
resolve(buffer);
|
resolve(buffer);
|
||||||
}.bind(this));
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -95,14 +94,14 @@ ADPCMSoundDecoder.prototype.indexTable = [
|
||||||
ADPCMSoundDecoder.prototype.extractChunk = function (chunkType, stream) {
|
ADPCMSoundDecoder.prototype.extractChunk = function (chunkType, stream) {
|
||||||
stream.position = 12;
|
stream.position = 12;
|
||||||
while (stream.position < (stream.getLength() - 8)) {
|
while (stream.position < (stream.getLength() - 8)) {
|
||||||
var typeStr = stream.readUint8String(4);
|
const typeStr = stream.readUint8String(4);
|
||||||
var chunkSize = stream.readInt32();
|
const chunkSize = stream.readInt32();
|
||||||
if (typeStr == chunkType) {
|
if (typeStr === chunkType) {
|
||||||
var chunk = stream.extract(chunkSize);
|
const chunk = stream.extract(chunkSize);
|
||||||
return chunk;
|
return chunk;
|
||||||
} else {
|
|
||||||
stream.position += chunkSize;
|
|
||||||
}
|
}
|
||||||
|
stream.position += chunkSize;
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -114,19 +113,22 @@ ADPCMSoundDecoder.prototype.extractChunk = function (chunkType, stream) {
|
||||||
* @return {Int16Array} the uncompressed audio samples
|
* @return {Int16Array} the uncompressed audio samples
|
||||||
*/
|
*/
|
||||||
ADPCMSoundDecoder.prototype.imaDecompress = function (compressedData, blockSize) {
|
ADPCMSoundDecoder.prototype.imaDecompress = function (compressedData, blockSize) {
|
||||||
var sample, step, code, delta;
|
let sample;
|
||||||
var index = 0;
|
let step;
|
||||||
var lastByte = -1; // -1 indicates that there is no saved lastByte
|
let code;
|
||||||
var out = [];
|
let delta;
|
||||||
|
let index = 0;
|
||||||
|
let lastByte = -1; // -1 indicates that there is no saved lastByte
|
||||||
|
const out = [];
|
||||||
|
|
||||||
// Bail and return no samples if we have no data
|
// Bail and return no samples if we have no data
|
||||||
if (!compressedData) return out;
|
if (!compressedData) return out;
|
||||||
|
|
||||||
compressedData.position = 0;
|
compressedData.position = 0;
|
||||||
var a = 0;
|
const a = 0;
|
||||||
while (a==0) {
|
while (a === 0) {
|
||||||
if (((compressedData.position % blockSize) == 0) && (lastByte < 0)) { // read block header
|
if (((compressedData.position % blockSize) === 0) && (lastByte < 0)) { // read block header
|
||||||
if (compressedData.getBytesAvailable() == 0) break;
|
if (compressedData.getBytesAvailable() === 0) break;
|
||||||
sample = compressedData.readInt16();
|
sample = compressedData.readInt16();
|
||||||
index = compressedData.readUint8();
|
index = compressedData.readUint8();
|
||||||
compressedData.position++; // skip extra header byte
|
compressedData.position++; // skip extra header byte
|
||||||
|
@ -135,7 +137,7 @@ ADPCMSoundDecoder.prototype.imaDecompress = function (compressedData, blockSize)
|
||||||
} else {
|
} else {
|
||||||
// read 4-bit code and compute delta from previous sample
|
// read 4-bit code and compute delta from previous sample
|
||||||
if (lastByte < 0) {
|
if (lastByte < 0) {
|
||||||
if (compressedData.getBytesAvailable() == 0) break;
|
if (compressedData.getBytesAvailable() === 0) break;
|
||||||
lastByte = compressedData.readUint8();
|
lastByte = compressedData.readUint8();
|
||||||
code = lastByte & 0xF;
|
code = lastByte & 0xF;
|
||||||
} else {
|
} else {
|
||||||
|
@ -159,7 +161,7 @@ ADPCMSoundDecoder.prototype.imaDecompress = function (compressedData, blockSize)
|
||||||
out.push(sample);
|
out.push(sample);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var samples = Int16Array.from(out);
|
const samples = Int16Array.from(out);
|
||||||
return samples;
|
return samples;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -8,10 +8,10 @@
|
||||||
* @param {ArrayBuffer} arrayBuffer - array to use as a stream
|
* @param {ArrayBuffer} arrayBuffer - array to use as a stream
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ArrayBufferStream (arrayBuffer) {
|
const ArrayBufferStream = function (arrayBuffer) {
|
||||||
this.arrayBuffer = arrayBuffer;
|
this.arrayBuffer = arrayBuffer;
|
||||||
this.position = 0;
|
this.position = 0;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Return a new ArrayBufferStream that is a slice of the existing one
|
* Return a new ArrayBufferStream that is a slice of the existing one
|
||||||
|
@ -19,8 +19,8 @@ function ArrayBufferStream (arrayBuffer) {
|
||||||
* @return {ArrayBufferStream} the extracted stream
|
* @return {ArrayBufferStream} the extracted stream
|
||||||
*/
|
*/
|
||||||
ArrayBufferStream.prototype.extract = function (length) {
|
ArrayBufferStream.prototype.extract = function (length) {
|
||||||
var slicedArrayBuffer = this.arrayBuffer.slice(this.position, this.position+length);
|
const slicedArrayBuffer = this.arrayBuffer.slice(this.position, this.position + length);
|
||||||
var newStream = new ArrayBufferStream(slicedArrayBuffer);
|
const newStream = new ArrayBufferStream(slicedArrayBuffer);
|
||||||
return newStream;
|
return newStream;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -40,10 +40,10 @@ ArrayBufferStream.prototype.getBytesAvailable = function () {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read an unsigned 8 bit integer from the stream
|
* Read an unsigned 8 bit integer from the stream
|
||||||
* @return {number}
|
* @return {number} the next 8 bit integer in the stream
|
||||||
*/
|
*/
|
||||||
ArrayBufferStream.prototype.readUint8 = function () {
|
ArrayBufferStream.prototype.readUint8 = function () {
|
||||||
var val = new Uint8Array(this.arrayBuffer, this.position, 1)[0];
|
const val = new Uint8Array(this.arrayBuffer, this.position, 1)[0];
|
||||||
this.position += 1;
|
this.position += 1;
|
||||||
return val;
|
return val;
|
||||||
};
|
};
|
||||||
|
@ -52,13 +52,13 @@ ArrayBufferStream.prototype.readUint8 = function () {
|
||||||
* Read a sequence of bytes of the given length and convert to a string.
|
* Read a sequence of bytes of the given length and convert to a string.
|
||||||
* This is a convenience method for use with short strings.
|
* This is a convenience method for use with short strings.
|
||||||
* @param {number} length - the number of bytes to convert
|
* @param {number} length - the number of bytes to convert
|
||||||
* @return {String} a String made by concatenating the chars in the input
|
* @return {string} a String made by concatenating the chars in the input
|
||||||
*/
|
*/
|
||||||
ArrayBufferStream.prototype.readUint8String = function (length) {
|
ArrayBufferStream.prototype.readUint8String = function (length) {
|
||||||
var arr = new Uint8Array(this.arrayBuffer, this.position, length);
|
const arr = new Uint8Array(this.arrayBuffer, this.position, length);
|
||||||
this.position += length;
|
this.position += length;
|
||||||
var str = '';
|
let str = '';
|
||||||
for (var i=0; i<arr.length; i++) {
|
for (let i = 0; i < arr.length; i++) {
|
||||||
str += String.fromCharCode(arr[i]);
|
str += String.fromCharCode(arr[i]);
|
||||||
}
|
}
|
||||||
return str;
|
return str;
|
||||||
|
@ -66,40 +66,40 @@ ArrayBufferStream.prototype.readUint8String = function (length) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a 16 bit integer from the stream
|
* Read a 16 bit integer from the stream
|
||||||
* @return {number}
|
* @return {number} the next 16 bit integer in the stream
|
||||||
*/
|
*/
|
||||||
ArrayBufferStream.prototype.readInt16 = function () {
|
ArrayBufferStream.prototype.readInt16 = function () {
|
||||||
var val = new Int16Array(this.arrayBuffer, this.position, 1)[0];
|
const val = new Int16Array(this.arrayBuffer, this.position, 1)[0];
|
||||||
this.position += 2; // one 16 bit int is 2 bytes
|
this.position += 2; // one 16 bit int is 2 bytes
|
||||||
return val;
|
return val;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read an unsigned 16 bit integer from the stream
|
* Read an unsigned 16 bit integer from the stream
|
||||||
* @return {number}
|
* @return {number} the next unsigned 16 bit integer in the stream
|
||||||
*/
|
*/
|
||||||
ArrayBufferStream.prototype.readUint16 = function () {
|
ArrayBufferStream.prototype.readUint16 = function () {
|
||||||
var val = new Uint16Array(this.arrayBuffer, this.position, 1)[0];
|
const val = new Uint16Array(this.arrayBuffer, this.position, 1)[0];
|
||||||
this.position += 2; // one 16 bit int is 2 bytes
|
this.position += 2; // one 16 bit int is 2 bytes
|
||||||
return val;
|
return val;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read a 32 bit integer from the stream
|
* Read a 32 bit integer from the stream
|
||||||
* @return {number}
|
* @return {number} the next 32 bit integer in the stream
|
||||||
*/
|
*/
|
||||||
ArrayBufferStream.prototype.readInt32 = function () {
|
ArrayBufferStream.prototype.readInt32 = function () {
|
||||||
var val = new Int32Array(this.arrayBuffer, this.position, 1)[0];
|
const val = new Int32Array(this.arrayBuffer, this.position, 1)[0];
|
||||||
this.position += 4; // one 32 bit int is 4 bytes
|
this.position += 4; // one 32 bit int is 4 bytes
|
||||||
return val;
|
return val;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Read an unsigned 32 bit integer from the stream
|
* Read an unsigned 32 bit integer from the stream
|
||||||
* @return {number}
|
* @return {number} the next unsigned 32 bit integer in the stream
|
||||||
*/
|
*/
|
||||||
ArrayBufferStream.prototype.readUint32 = function () {
|
ArrayBufferStream.prototype.readUint32 = function () {
|
||||||
var val = new Uint32Array(this.arrayBuffer, this.position, 1)[0];
|
const val = new Uint32Array(this.arrayBuffer, this.position, 1)[0];
|
||||||
this.position += 4; // one 32 bit int is 4 bytes
|
this.position += 4; // one 32 bit int is 4 bytes
|
||||||
return val;
|
return val;
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,16 +1,16 @@
|
||||||
var SoundPlayer = require('./SoundPlayer');
|
const SoundPlayer = require('./SoundPlayer');
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A prototype for the drum sound functionality that can load drum sounds, play, and stop them.
|
* A prototype for the drum sound functionality that can load drum sounds, play, and stop them.
|
||||||
* @param {Tone.Gain} outputNode - a webAudio node that the drum sounds will send their output to
|
* @param {Tone.Gain} outputNode - a webAudio node that the drum sounds will send their output to
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function DrumPlayer (outputNode) {
|
const DrumPlayer = function (outputNode) {
|
||||||
this.outputNode = outputNode;
|
this.outputNode = outputNode;
|
||||||
|
|
||||||
var baseUrl = 'https://raw.githubusercontent.com/LLK/scratch-audio/develop/sound-files/drums/';
|
const baseUrl = 'https://raw.githubusercontent.com/LLK/scratch-audio/develop/sound-files/drums/';
|
||||||
var fileNames = [
|
const fileNames = [
|
||||||
'SnareDrum(1)',
|
'SnareDrum(1)',
|
||||||
'BassDrum(1b)',
|
'BassDrum(1b)',
|
||||||
'SideStick(1)',
|
'SideStick(1)',
|
||||||
|
@ -33,12 +33,12 @@ function DrumPlayer (outputNode) {
|
||||||
|
|
||||||
this.drumSounds = [];
|
this.drumSounds = [];
|
||||||
|
|
||||||
for (var i=0; i<fileNames.length; i++) {
|
for (let i = 0; i < fileNames.length; i++) {
|
||||||
var url = baseUrl + fileNames[i] + '_22k.wav';
|
const url = `${baseUrl + fileNames[i]}_22k.wav`;
|
||||||
this.drumSounds[i] = new SoundPlayer(this.outputNode);
|
this.drumSounds[i] = new SoundPlayer(this.outputNode);
|
||||||
this.drumSounds[i].setBuffer(new Tone.Buffer(url));
|
this.drumSounds[i].setBuffer(new Tone.Buffer(url));
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play a drum sound.
|
* Play a drum sound.
|
||||||
|
@ -56,7 +56,7 @@ DrumPlayer.prototype.play = function (drum, outputNode) {
|
||||||
* Stop all drum sounds.
|
* Stop all drum sounds.
|
||||||
*/
|
*/
|
||||||
DrumPlayer.prototype.stopAll = function () {
|
DrumPlayer.prototype.stopAll = function () {
|
||||||
for (var i=0; i<this.drumSounds.length; i++) {
|
for (let i = 0; i < this.drumSounds.length; i++) {
|
||||||
this.drumSounds[i].stop();
|
this.drumSounds[i].stop();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
var Soundfont = require('soundfont-player');
|
const Soundfont = require('soundfont-player');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A prototype for the instrument sound functionality that can play notes.
|
* A prototype for the instrument sound functionality that can play notes.
|
||||||
|
@ -12,7 +12,7 @@ var Soundfont = require('soundfont-player');
|
||||||
* @param {Tone.Gain} outputNode - a webAudio node that the instrument will send its output to
|
* @param {Tone.Gain} outputNode - a webAudio node that the instrument will send its output to
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function InstrumentPlayer (outputNode) {
|
const InstrumentPlayer = function (outputNode) {
|
||||||
this.outputNode = outputNode;
|
this.outputNode = outputNode;
|
||||||
|
|
||||||
// Instrument names used by Musyng Kite soundfont, in order to
|
// Instrument names used by Musyng Kite soundfont, in order to
|
||||||
|
@ -24,7 +24,7 @@ function InstrumentPlayer (outputNode) {
|
||||||
'music_box', 'steel_drums', 'marimba', 'lead_1_square', 'fx_4_atmosphere'];
|
'music_box', 'steel_drums', 'marimba', 'lead_1_square', 'fx_4_atmosphere'];
|
||||||
|
|
||||||
this.instruments = [];
|
this.instruments = [];
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play a note for some number of seconds with a particular instrument.
|
* Play a note for some number of seconds with a particular instrument.
|
||||||
|
@ -37,13 +37,13 @@ function InstrumentPlayer (outputNode) {
|
||||||
* @param {number} vol - a volume level (0-100%)
|
* @param {number} vol - a volume level (0-100%)
|
||||||
*/
|
*/
|
||||||
InstrumentPlayer.prototype.playNoteForSecWithInstAndVol = function (note, sec, instrumentNum, vol) {
|
InstrumentPlayer.prototype.playNoteForSecWithInstAndVol = function (note, sec, instrumentNum, vol) {
|
||||||
var gain = vol / 100;
|
const gain = vol / 100;
|
||||||
this.loadInstrument(instrumentNum)
|
this.loadInstrument(instrumentNum)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
this.instruments[instrumentNum].play(
|
this.instruments[instrumentNum].play(
|
||||||
note, Tone.context.currentTime, {
|
note, Tone.context.currentTime, {
|
||||||
duration : sec,
|
duration: sec,
|
||||||
gain : gain
|
gain: gain
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -57,20 +57,20 @@ InstrumentPlayer.prototype.playNoteForSecWithInstAndVol = function (note, sec, i
|
||||||
InstrumentPlayer.prototype.loadInstrument = function (instrumentNum) {
|
InstrumentPlayer.prototype.loadInstrument = function (instrumentNum) {
|
||||||
if (this.instruments[instrumentNum]) {
|
if (this.instruments[instrumentNum]) {
|
||||||
return Promise.resolve();
|
return Promise.resolve();
|
||||||
} else {
|
}
|
||||||
return Soundfont.instrument(Tone.context, this.instrumentNames[instrumentNum])
|
return Soundfont.instrument(Tone.context, this.instrumentNames[instrumentNum])
|
||||||
.then((inst) => {
|
.then(inst => {
|
||||||
inst.connect(this.outputNode);
|
inst.connect(this.outputNode);
|
||||||
this.instruments[instrumentNum] = inst;
|
this.instruments[instrumentNum] = inst;
|
||||||
});
|
});
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop all notes being played on all instruments
|
* Stop all notes being played on all instruments
|
||||||
*/
|
*/
|
||||||
InstrumentPlayer.prototype.stopAll = function () {
|
InstrumentPlayer.prototype.stopAll = function () {
|
||||||
for (var i=0; i<this.instruments.length; i++) {
|
for (let i = 0; i < this.instruments.length; i++) {
|
||||||
if (this.instruments[i]) {
|
if (this.instruments[i]) {
|
||||||
this.instruments[i].stop();
|
this.instruments[i].stop();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,17 @@
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
var log = require('./log');
|
const log = require('./log');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A SoundPlayer stores an audio buffer, and plays it
|
* A SoundPlayer stores an audio buffer, and plays it
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function SoundPlayer () {
|
const SoundPlayer = function () {
|
||||||
this.outputNode = null;
|
this.outputNode = null;
|
||||||
this.buffer = new Tone.Buffer();
|
this.buffer = new Tone.Buffer();
|
||||||
this.bufferSource = null;
|
this.bufferSource = null;
|
||||||
this.playbackRate = 1;
|
this.playbackRate = 1;
|
||||||
this.isPlaying = false;
|
this.isPlaying = false;
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Connect the SoundPlayer to an output node
|
* Connect the SoundPlayer to an output node
|
||||||
|
@ -23,7 +23,7 @@ SoundPlayer.prototype.connect = function (node) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set an audio buffer
|
* Set an audio buffer
|
||||||
* @param {Tone.Buffer} buffer
|
* @param {Tone.Buffer} buffer Buffer to set
|
||||||
*/
|
*/
|
||||||
SoundPlayer.prototype.setBuffer = function (buffer) {
|
SoundPlayer.prototype.setBuffer = function (buffer) {
|
||||||
this.buffer = buffer;
|
this.buffer = buffer;
|
||||||
|
@ -74,8 +74,8 @@ SoundPlayer.prototype.start = function () {
|
||||||
* @return {Promise} a Promise that resolves when the sound finishes playing
|
* @return {Promise} a Promise that resolves when the sound finishes playing
|
||||||
*/
|
*/
|
||||||
SoundPlayer.prototype.finished = function () {
|
SoundPlayer.prototype.finished = function () {
|
||||||
var storedContext = this;
|
const storedContext = this;
|
||||||
return new Promise(function (resolve) {
|
return new Promise(resolve => {
|
||||||
storedContext.bufferSource.onended = function () {
|
storedContext.bufferSource.onended = function () {
|
||||||
this.isPlaying = false;
|
this.isPlaying = false;
|
||||||
resolve();
|
resolve();
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An echo effect (aka 'delay effect' in audio terms)
|
* An echo effect (aka 'delay effect' in audio terms)
|
||||||
|
@ -8,7 +8,7 @@ var Tone = require('tone');
|
||||||
* Clamped 0-100
|
* Clamped 0-100
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function EchoEffect () {
|
const EchoEffect = function () {
|
||||||
Tone.Effect.call(this);
|
Tone.Effect.call(this);
|
||||||
|
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
|
@ -16,7 +16,7 @@ function EchoEffect () {
|
||||||
this.delay = new Tone.FeedbackDelay(0.25, 0.5);
|
this.delay = new Tone.FeedbackDelay(0.25, 0.5);
|
||||||
|
|
||||||
this.effectSend.chain(this.delay, this.effectReturn);
|
this.effectSend.chain(this.delay, this.effectReturn);
|
||||||
}
|
};
|
||||||
|
|
||||||
Tone.extend(EchoEffect, Tone.Effect);
|
Tone.extend(EchoEffect, Tone.Effect);
|
||||||
|
|
||||||
|
@ -30,14 +30,14 @@ EchoEffect.prototype.set = function (val) {
|
||||||
this.value = this.clamp(this.value, 0, 100);
|
this.value = this.clamp(this.value, 0, 100);
|
||||||
|
|
||||||
// mute the effect if value is 0
|
// mute the effect if value is 0
|
||||||
if (this.value == 0) {
|
if (this.value === 0) {
|
||||||
this.wet.value = 0;
|
this.wet.value = 0;
|
||||||
} else {
|
} else {
|
||||||
this.wet.value = 0.5;
|
this.wet.value = 0.5;
|
||||||
}
|
}
|
||||||
|
|
||||||
var feedback = (this.value / 100) * 0.75;
|
const feedback = (this.value / 100) * 0.75;
|
||||||
this.delay.feedback.rampTo(feedback, 1/60);
|
this.delay.feedback.rampTo(feedback, 1 / 60);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -53,10 +53,10 @@ EchoEffect.prototype.changeBy = function (val) {
|
||||||
* @param {number} input - the input to clamp
|
* @param {number} input - the input to clamp
|
||||||
* @param {number} min - the min value to clamp to
|
* @param {number} min - the min value to clamp to
|
||||||
* @param {number} max - the max value to clamp to
|
* @param {number} max - the max value to clamp to
|
||||||
|
* @return {number} the clamped value
|
||||||
*/
|
*/
|
||||||
EchoEffect.prototype.clamp = function (input, min, max) {
|
EchoEffect.prototype.clamp = function (input, min, max) {
|
||||||
return Math.min(Math.max(input, min), max);
|
return Math.min(Math.max(input, min), max);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = EchoEffect;
|
module.exports = EchoEffect;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A fuzz effect (aka 'distortion effect' in audio terms)
|
* A fuzz effect (aka 'distortion effect' in audio terms)
|
||||||
|
@ -7,7 +7,7 @@ var Tone = require('tone');
|
||||||
* Clamped 0-100
|
* Clamped 0-100
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function FuzzEffect () {
|
const FuzzEffect = function () {
|
||||||
Tone.Effect.call(this);
|
Tone.Effect.call(this);
|
||||||
|
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
|
@ -15,7 +15,7 @@ function FuzzEffect () {
|
||||||
this.distortion = new Tone.Distortion(1);
|
this.distortion = new Tone.Distortion(1);
|
||||||
|
|
||||||
this.effectSend.chain(this.distortion, this.effectReturn);
|
this.effectSend.chain(this.distortion, this.effectReturn);
|
||||||
}
|
};
|
||||||
|
|
||||||
Tone.extend(FuzzEffect, Tone.Effect);
|
Tone.extend(FuzzEffect, Tone.Effect);
|
||||||
|
|
||||||
|
@ -43,10 +43,10 @@ FuzzEffect.prototype.changeBy = function (val) {
|
||||||
* @param {number} input - the input to clamp
|
* @param {number} input - the input to clamp
|
||||||
* @param {number} min - the min value to clamp to
|
* @param {number} min - the min value to clamp to
|
||||||
* @param {number} max - the max value to clamp to
|
* @param {number} max - the max value to clamp to
|
||||||
|
* @return {number} the clamped value
|
||||||
*/
|
*/
|
||||||
FuzzEffect.prototype.clamp = function (input, min, max) {
|
FuzzEffect.prototype.clamp = function (input, min, max) {
|
||||||
return Math.min(Math.max(input, min), max);
|
return Math.min(Math.max(input, min), max);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = FuzzEffect;
|
module.exports = FuzzEffect;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pan effect, which moves the sound to the left or right between the speakers
|
* A pan effect, which moves the sound to the left or right between the speakers
|
||||||
|
@ -7,7 +7,7 @@ var Tone = require('tone');
|
||||||
* Clamped -100 to 100
|
* Clamped -100 to 100
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function PanEffect () {
|
const PanEffect = function () {
|
||||||
Tone.Effect.call(this);
|
Tone.Effect.call(this);
|
||||||
|
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
|
@ -15,7 +15,7 @@ function PanEffect () {
|
||||||
this.panner = new Tone.Panner();
|
this.panner = new Tone.Panner();
|
||||||
|
|
||||||
this.effectSend.chain(this.panner, this.effectReturn);
|
this.effectSend.chain(this.panner, this.effectReturn);
|
||||||
}
|
};
|
||||||
|
|
||||||
Tone.extend(PanEffect, Tone.Effect);
|
Tone.extend(PanEffect, Tone.Effect);
|
||||||
|
|
||||||
|
@ -44,10 +44,10 @@ PanEffect.prototype.changeBy = function (val) {
|
||||||
* @param {number} input - the input to clamp
|
* @param {number} input - the input to clamp
|
||||||
* @param {number} min - the min value to clamp to
|
* @param {number} min - the min value to clamp to
|
||||||
* @param {number} max - the max value to clamp to
|
* @param {number} max - the max value to clamp to
|
||||||
|
* @return {number} the clamped value
|
||||||
*/
|
*/
|
||||||
PanEffect.prototype.clamp = function (input, min, max) {
|
PanEffect.prototype.clamp = function (input, min, max) {
|
||||||
return Math.min(Math.max(input, min), max);
|
return Math.min(Math.max(input, min), max);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = PanEffect;
|
module.exports = PanEffect;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A pitch change effect, which changes the playback rate of the sound in order
|
* A pitch change effect, which changes the playback rate of the sound in order
|
||||||
|
@ -18,12 +18,12 @@ var Tone = require('tone');
|
||||||
* on one SoundPlayer or a group of them.
|
* on one SoundPlayer or a group of them.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function PitchEffect () {
|
const PitchEffect = function () {
|
||||||
this.value = 0; // effect value
|
this.value = 0; // effect value
|
||||||
this.ratio = 1; // the playback rate ratio
|
this.ratio = 1; // the playback rate ratio
|
||||||
|
|
||||||
this.tone = new Tone();
|
this.tone = new Tone();
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Set the effect value
|
* Set the effect value
|
||||||
|
@ -39,7 +39,7 @@ PitchEffect.prototype.set = function (val, players) {
|
||||||
/**
|
/**
|
||||||
* Change the effect value
|
* Change the effect value
|
||||||
* @param {number} val - the value to change the effect by
|
* @param {number} val - the value to change the effect by
|
||||||
* @param {Object} players - a dictionary of SoundPlayer objects indexed by md5
|
* @param {object} players - a dictionary of SoundPlayer objects indexed by md5
|
||||||
*/
|
*/
|
||||||
PitchEffect.prototype.changeBy = function (val, players) {
|
PitchEffect.prototype.changeBy = function (val, players) {
|
||||||
this.set(this.value + val, players);
|
this.set(this.value + val, players);
|
||||||
|
@ -58,7 +58,7 @@ PitchEffect.prototype.getRatio = function (val) {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Update a sound player's playback rate using the current ratio for the effect
|
* Update a sound player's playback rate using the current ratio for the effect
|
||||||
* @param {Object} player - a SoundPlayer object
|
* @param {object} player - a SoundPlayer object
|
||||||
*/
|
*/
|
||||||
PitchEffect.prototype.updatePlayer = function (player) {
|
PitchEffect.prototype.updatePlayer = function (player) {
|
||||||
player.setPlaybackRate(this.ratio);
|
player.setPlaybackRate(this.ratio);
|
||||||
|
@ -71,7 +71,7 @@ PitchEffect.prototype.updatePlayer = function (player) {
|
||||||
PitchEffect.prototype.updatePlayers = function (players) {
|
PitchEffect.prototype.updatePlayers = function (players) {
|
||||||
if (!players) return;
|
if (!players) return;
|
||||||
|
|
||||||
for (var md5 in players) {
|
for (const md5 in players) {
|
||||||
if (players.hasOwnProperty(md5)) {
|
if (players.hasOwnProperty(md5)) {
|
||||||
this.updatePlayer(players[md5]);
|
this.updatePlayer(players[md5]);
|
||||||
}
|
}
|
||||||
|
@ -79,4 +79,3 @@ PitchEffect.prototype.updatePlayers = function (players) {
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = PitchEffect;
|
module.exports = PitchEffect;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A reverb effect, simulating reverberation in a room
|
* A reverb effect, simulating reverberation in a room
|
||||||
|
@ -7,7 +7,7 @@ var Tone = require('tone');
|
||||||
* Clamped 0 to 100
|
* Clamped 0 to 100
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function ReverbEffect () {
|
const ReverbEffect = function () {
|
||||||
Tone.Effect.call(this);
|
Tone.Effect.call(this);
|
||||||
|
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
|
@ -15,7 +15,7 @@ function ReverbEffect () {
|
||||||
this.reverb = new Tone.Freeverb();
|
this.reverb = new Tone.Freeverb();
|
||||||
|
|
||||||
this.effectSend.chain(this.reverb, this.effectReturn);
|
this.effectSend.chain(this.reverb, this.effectReturn);
|
||||||
}
|
};
|
||||||
|
|
||||||
Tone.extend(ReverbEffect, Tone.Effect);
|
Tone.extend(ReverbEffect, Tone.Effect);
|
||||||
|
|
||||||
|
@ -44,10 +44,10 @@ ReverbEffect.prototype.changeBy = function (val) {
|
||||||
* @param {number} input - the input to clamp
|
* @param {number} input - the input to clamp
|
||||||
* @param {number} min - the min value to clamp to
|
* @param {number} min - the min value to clamp to
|
||||||
* @param {number} max - the max value to clamp to
|
* @param {number} max - the max value to clamp to
|
||||||
|
* @return {number} the clamped value
|
||||||
*/
|
*/
|
||||||
ReverbEffect.prototype.clamp = function (input, min, max) {
|
ReverbEffect.prototype.clamp = function (input, min, max) {
|
||||||
return Math.min(Math.max(input, min), max);
|
return Math.min(Math.max(input, min), max);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = ReverbEffect;
|
module.exports = ReverbEffect;
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
const Tone = require('tone');
|
||||||
var Tone = require('tone');
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A "robotic" effect that adds a low-pitched buzzing to the sound, reminiscent of the
|
* A "robotic" effect that adds a low-pitched buzzing to the sound, reminiscent of the
|
||||||
|
@ -12,16 +11,16 @@ var Tone = require('tone');
|
||||||
* Exterminate.
|
* Exterminate.
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function RoboticEffect () {
|
const RoboticEffect = function () {
|
||||||
Tone.Effect.call(this);
|
Tone.Effect.call(this);
|
||||||
|
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
|
|
||||||
var time = this._delayTimeForValue(100);
|
const time = this._delayTimeForValue(100);
|
||||||
this.feedbackCombFilter = new Tone.FeedbackCombFilter(time, 0.9);
|
this.feedbackCombFilter = new Tone.FeedbackCombFilter(time, 0.9);
|
||||||
|
|
||||||
this.effectSend.chain(this.feedbackCombFilter, this.effectReturn);
|
this.effectSend.chain(this.feedbackCombFilter, this.effectReturn);
|
||||||
}
|
};
|
||||||
|
|
||||||
Tone.extend(RoboticEffect, Tone.Effect);
|
Tone.extend(RoboticEffect, Tone.Effect);
|
||||||
|
|
||||||
|
@ -33,15 +32,15 @@ RoboticEffect.prototype.set = function (val) {
|
||||||
this.value = val;
|
this.value = val;
|
||||||
|
|
||||||
// mute the effect if value is 0
|
// mute the effect if value is 0
|
||||||
if (this.value == 0) {
|
if (this.value === 0) {
|
||||||
this.wet.value = 0;
|
this.wet.value = 0;
|
||||||
} else {
|
} else {
|
||||||
this.wet.value = 1;
|
this.wet.value = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// set delay time using the value
|
// set delay time using the value
|
||||||
var time = this._delayTimeForValue(this.value);
|
const time = this._delayTimeForValue(this.value);
|
||||||
this.feedbackCombFilter.delayTime.rampTo(time, 1/60);
|
this.feedbackCombFilter.delayTime.rampTo(time, 1 / 60);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -60,10 +59,9 @@ RoboticEffect.prototype.changeBy = function (val) {
|
||||||
* @returns {number} a delay time in seconds
|
* @returns {number} a delay time in seconds
|
||||||
*/
|
*/
|
||||||
RoboticEffect.prototype._delayTimeForValue = function (val) {
|
RoboticEffect.prototype._delayTimeForValue = function (val) {
|
||||||
var midiNote = ((val - 100) / 10) + 36;
|
const midiNote = ((val - 100) / 10) + 36;
|
||||||
var freq = Tone.Frequency(midiNote, 'midi').eval();
|
const freq = Tone.Frequency(midiNote, 'midi').eval();
|
||||||
return 1 / freq;
|
return 1 / freq;
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = RoboticEffect;
|
module.exports = RoboticEffect;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A wobble effect. In audio terms, it sounds like tremolo.
|
* A wobble effect. In audio terms, it sounds like tremolo.
|
||||||
|
@ -11,7 +11,7 @@ var Tone = require('tone');
|
||||||
* Clamped 0 to 100
|
* Clamped 0 to 100
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function WobbleEffect () {
|
const WobbleEffect = function () {
|
||||||
Tone.Effect.call(this);
|
Tone.Effect.call(this);
|
||||||
|
|
||||||
this.value = 0;
|
this.value = 0;
|
||||||
|
@ -21,7 +21,7 @@ function WobbleEffect () {
|
||||||
this.wobbleLFO.connect(this.wobbleGain.gain);
|
this.wobbleLFO.connect(this.wobbleGain.gain);
|
||||||
|
|
||||||
this.effectSend.chain(this.wobbleGain, this.effectReturn);
|
this.effectSend.chain(this.wobbleGain, this.effectReturn);
|
||||||
}
|
};
|
||||||
|
|
||||||
Tone.extend(WobbleEffect, Tone.Effect);
|
Tone.extend(WobbleEffect, Tone.Effect);
|
||||||
|
|
||||||
|
@ -36,7 +36,7 @@ WobbleEffect.prototype.set = function (val) {
|
||||||
|
|
||||||
this.wet.value = this.value / 100;
|
this.wet.value = this.value / 100;
|
||||||
|
|
||||||
this.wobbleLFO.frequency.rampTo(this.value / 10, 1/60);
|
this.wobbleLFO.frequency.rampTo(this.value / 10, 1 / 60);
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,10 +52,10 @@ WobbleEffect.prototype.changeBy = function (val) {
|
||||||
* @param {number} input - the input to clamp
|
* @param {number} input - the input to clamp
|
||||||
* @param {number} min - the min value to clamp to
|
* @param {number} min - the min value to clamp to
|
||||||
* @param {number} max - the max value to clamp to
|
* @param {number} max - the max value to clamp to
|
||||||
|
* @return {number} the clamped value
|
||||||
*/
|
*/
|
||||||
WobbleEffect.prototype.clamp = function (input, min, max) {
|
WobbleEffect.prototype.clamp = function (input, min, max) {
|
||||||
return Math.min(Math.max(input, min), max);
|
return Math.min(Math.max(input, min), max);
|
||||||
};
|
};
|
||||||
|
|
||||||
module.exports = WobbleEffect;
|
module.exports = WobbleEffect;
|
||||||
|
|
||||||
|
|
398
src/index.js
398
src/index.js
|
@ -1,213 +1,32 @@
|
||||||
var log = require('./log');
|
const log = require('./log');
|
||||||
var Tone = require('tone');
|
const Tone = require('tone');
|
||||||
|
|
||||||
var PitchEffect = require('./effects/PitchEffect');
|
const PitchEffect = require('./effects/PitchEffect');
|
||||||
var PanEffect = require('./effects/PanEffect');
|
const PanEffect = require('./effects/PanEffect');
|
||||||
|
|
||||||
var RoboticEffect = require('./effects/RoboticEffect');
|
const RoboticEffect = require('./effects/RoboticEffect');
|
||||||
var FuzzEffect = require('./effects/FuzzEffect');
|
const FuzzEffect = require('./effects/FuzzEffect');
|
||||||
var EchoEffect = require('./effects/EchoEffect');
|
const EchoEffect = require('./effects/EchoEffect');
|
||||||
var ReverbEffect = require('./effects/ReverbEffect');
|
const ReverbEffect = require('./effects/ReverbEffect');
|
||||||
|
|
||||||
var SoundPlayer = require('./SoundPlayer');
|
const SoundPlayer = require('./SoundPlayer');
|
||||||
var ADPCMSoundDecoder = require('./ADPCMSoundDecoder');
|
const ADPCMSoundDecoder = require('./ADPCMSoundDecoder');
|
||||||
var InstrumentPlayer = require('./InstrumentPlayer');
|
const InstrumentPlayer = require('./InstrumentPlayer');
|
||||||
var DrumPlayer = require('./DrumPlayer');
|
const DrumPlayer = require('./DrumPlayer');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @fileOverview Scratch Audio is divided into a single AudioEngine,
|
* @fileOverview Scratch Audio is divided into a single AudioEngine,
|
||||||
* that handles global functionality, and AudioPlayers, belonging to individual sprites and clones.
|
* that handles global functionality, and AudioPlayers, belonging to individual sprites and clones.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
/**
|
|
||||||
* There is a single instance of the AudioEngine. It handles global audio properties and effects,
|
|
||||||
* loads all the audio buffers for sounds belonging to sprites, and creates a single instrument player
|
|
||||||
* and a drum player, used by all play note and play drum blocks.
|
|
||||||
* @constructor
|
|
||||||
*/
|
|
||||||
function AudioEngine () {
|
|
||||||
|
|
||||||
// create the global audio effects
|
|
||||||
this.roboticEffect = new RoboticEffect();
|
|
||||||
this.fuzzEffect = new FuzzEffect();
|
|
||||||
this.echoEffect = new EchoEffect();
|
|
||||||
this.reverbEffect = new ReverbEffect();
|
|
||||||
|
|
||||||
// chain the global effects to the output
|
|
||||||
this.input = new Tone.Gain();
|
|
||||||
this.input.chain (
|
|
||||||
this.roboticEffect, this.fuzzEffect, this.echoEffect, this.reverbEffect,
|
|
||||||
Tone.Master
|
|
||||||
);
|
|
||||||
|
|
||||||
// global tempo in bpm (beats per minute)
|
|
||||||
this.currentTempo = 60;
|
|
||||||
|
|
||||||
// instrument player for play note blocks
|
|
||||||
this.instrumentPlayer = new InstrumentPlayer(this.input);
|
|
||||||
this.numInstruments = this.instrumentPlayer.instrumentNames.length;
|
|
||||||
|
|
||||||
// drum player for play drum blocks
|
|
||||||
this.drumPlayer = new DrumPlayer(this.input);
|
|
||||||
this.numDrums = this.drumPlayer.drumSounds.length;
|
|
||||||
|
|
||||||
// a map of md5s to audio buffers, holding sounds for all sprites
|
|
||||||
this.audioBuffers = {};
|
|
||||||
|
|
||||||
// microphone, for measuring loudness, with a level meter analyzer
|
|
||||||
this.mic = null;
|
|
||||||
this.micMeter = null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Decode a sound, decompressing it into audio samples.
|
|
||||||
* Store a reference to it the sound in the audioBuffers dictionary, indexed by md5
|
|
||||||
* @param {Object} sound - an object containing audio data and metadata for a sound
|
|
||||||
* @property {Buffer} data - sound data loaded from scratch-storage.
|
|
||||||
* @property {string} format - format type, either empty or adpcm.
|
|
||||||
* @property {string} md5 - the MD5 and extension of the sound.
|
|
||||||
* @returns {?Promise} - a promise which will resolve after the audio buffer is stored, or null on error.
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.decodeSound = function (sound) {
|
|
||||||
|
|
||||||
var loaderPromise = null;
|
|
||||||
|
|
||||||
switch (sound.format) {
|
|
||||||
case '':
|
|
||||||
loaderPromise = Tone.context.decodeAudioData(sound.data.buffer);
|
|
||||||
break;
|
|
||||||
case 'adpcm':
|
|
||||||
loaderPromise = (new ADPCMSoundDecoder()).decode(sound.data.buffer);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
return log.warn('unknown sound format', sound.format);
|
|
||||||
}
|
|
||||||
|
|
||||||
var storedContext = this;
|
|
||||||
return loaderPromise.then(
|
|
||||||
function (decodedAudio) {
|
|
||||||
storedContext.audioBuffers[sound.md5] = new Tone.Buffer(decodedAudio);
|
|
||||||
},
|
|
||||||
function (error) {
|
|
||||||
log.warn('audio data could not be decoded', error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An older version of the AudioEngine had this function to load all sounds
|
|
||||||
* This is a stub to provide a warning when it is called
|
|
||||||
* @todo remove this
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.loadSounds = function () {
|
|
||||||
log.warn('The loadSounds function is no longer available. Please use Scratch Storage.');
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Play a note for a duration on an instrument with a volume
|
|
||||||
* @param {number} note - a MIDI note number
|
|
||||||
* @param {number} beats - a duration in beats
|
|
||||||
* @param {number} inst - an instrument number (0-indexed)
|
|
||||||
* @param {number} vol - a volume level (0-100%)
|
|
||||||
* @return {Promise} a Promise that resolves after the duration has elapsed
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.playNoteForBeatsWithInstAndVol = function (note, beats, inst, vol) {
|
|
||||||
var sec = this.beatsToSec(beats);
|
|
||||||
this.instrumentPlayer.playNoteForSecWithInstAndVol(note, sec, inst, vol);
|
|
||||||
return this.waitForBeats(beats);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Convert a number of beats to a number of seconds, using the current tempo
|
|
||||||
* @param {number} beats
|
|
||||||
* @return {number} seconds
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.beatsToSec = function (beats) {
|
|
||||||
return (60 / this.currentTempo) * beats;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Wait for some number of beats
|
|
||||||
* @param {number} beats
|
|
||||||
* @return {Promise} a Promise that resolves after the duration has elapsed
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.waitForBeats = function (beats) {
|
|
||||||
var storedContext = this;
|
|
||||||
return new Promise(function (resolve) {
|
|
||||||
setTimeout(function () {
|
|
||||||
resolve();
|
|
||||||
}, storedContext.beatsToSec(beats) * 1000);
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Set the global tempo in bpm (beats per minute)
|
|
||||||
* @param {number} value - the new tempo to set
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.setTempo = function (value) {
|
|
||||||
this.currentTempo = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Change the tempo by some number of bpm (beats per minute)
|
|
||||||
* @param {number} value - the number of bpm to change the tempo by
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.changeTempo = function (value) {
|
|
||||||
this.setTempo(this.currentTempo + value);
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get the current loudness of sound received by the microphone.
|
|
||||||
* Sound is measured in RMS and smoothed.
|
|
||||||
* @return {number} loudness scaled 0 to 100
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.getLoudness = function () {
|
|
||||||
if (!this.mic) {
|
|
||||||
this.mic = new Tone.UserMedia();
|
|
||||||
this.micMeter = new Tone.Meter('level', 0.5);
|
|
||||||
this.mic.open();
|
|
||||||
this.mic.connect(this.micMeter);
|
|
||||||
}
|
|
||||||
if (this.mic && this.mic.state == 'started') {
|
|
||||||
return this.micMeter.value * 100;
|
|
||||||
} else {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Names of the audio effects.
|
|
||||||
* @readonly
|
|
||||||
* @enum {string}
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.EFFECT_NAMES = {
|
|
||||||
pitch: 'pitch',
|
|
||||||
pan: 'pan',
|
|
||||||
echo: 'echo',
|
|
||||||
reverb: 'reverb',
|
|
||||||
fuzz: 'fuzz',
|
|
||||||
robot: 'robot'
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Create an AudioPlayer. Each sprite or clone has an AudioPlayer.
|
|
||||||
* It includes a reference to the AudioEngine so it can use global
|
|
||||||
* functionality such as playing notes.
|
|
||||||
* @return {AudioPlayer}
|
|
||||||
*/
|
|
||||||
AudioEngine.prototype.createPlayer = function () {
|
|
||||||
return new AudioPlayer(this);
|
|
||||||
};
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Each sprite or clone has an audio player
|
* Each sprite or clone has an audio player
|
||||||
* the audio player handles sound playback, volume, and the sprite-specific audio effects:
|
* the audio player handles sound playback, volume, and the sprite-specific audio effects:
|
||||||
* pitch and pan
|
* pitch and pan
|
||||||
* @param {AudioEngine}
|
* @param {AudioEngine} audioEngine AudioEngine for player
|
||||||
* @constructor
|
* @constructor
|
||||||
*/
|
*/
|
||||||
function AudioPlayer (audioEngine) {
|
const AudioPlayer = function (audioEngine) {
|
||||||
|
|
||||||
this.audioEngine = audioEngine;
|
this.audioEngine = audioEngine;
|
||||||
|
|
||||||
|
@ -226,7 +45,7 @@ function AudioPlayer (audioEngine) {
|
||||||
|
|
||||||
// sound players that are currently playing, indexed by the sound's md5
|
// sound players that are currently playing, indexed by the sound's md5
|
||||||
this.activeSoundPlayers = {};
|
this.activeSoundPlayers = {};
|
||||||
}
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Play a sound
|
* Play a sound
|
||||||
|
@ -245,7 +64,7 @@ AudioPlayer.prototype.playSound = function (md5) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// create a new soundplayer to play the sound
|
// create a new soundplayer to play the sound
|
||||||
var player = new SoundPlayer();
|
const player = new SoundPlayer();
|
||||||
player.setBuffer(this.audioEngine.audioBuffers[md5]);
|
player.setBuffer(this.audioEngine.audioBuffers[md5]);
|
||||||
player.connect(this.effectsNode);
|
player.connect(this.effectsNode);
|
||||||
this.pitchEffect.updatePlayer(player);
|
this.pitchEffect.updatePlayer(player);
|
||||||
|
@ -255,7 +74,7 @@ AudioPlayer.prototype.playSound = function (md5) {
|
||||||
this.activeSoundPlayers[md5] = player;
|
this.activeSoundPlayers[md5] = player;
|
||||||
|
|
||||||
// remove sounds that are not playing from the active sound players array
|
// remove sounds that are not playing from the active sound players array
|
||||||
for (var id in this.activeSoundPlayers) {
|
for (const id in this.activeSoundPlayers) {
|
||||||
if (this.activeSoundPlayers.hasOwnProperty(id)) {
|
if (this.activeSoundPlayers.hasOwnProperty(id)) {
|
||||||
if (!this.activeSoundPlayers[id].isPlaying) {
|
if (!this.activeSoundPlayers[id].isPlaying) {
|
||||||
delete this.activeSoundPlayers[id];
|
delete this.activeSoundPlayers[id];
|
||||||
|
@ -283,7 +102,7 @@ AudioPlayer.prototype.playDrumForBeats = function (drum, beats) {
|
||||||
*/
|
*/
|
||||||
AudioPlayer.prototype.stopAllSounds = function () {
|
AudioPlayer.prototype.stopAllSounds = function () {
|
||||||
// stop all active sound players
|
// stop all active sound players
|
||||||
for (var md5 in this.activeSoundPlayers) {
|
for (const md5 in this.activeSoundPlayers) {
|
||||||
this.activeSoundPlayers[md5].stop();
|
this.activeSoundPlayers[md5].stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -344,4 +163,185 @@ AudioPlayer.prototype.setVolume = function (value) {
|
||||||
this.effectsNode.gain.value = value / 100;
|
this.effectsNode.gain.value = value / 100;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* There is a single instance of the AudioEngine. It handles global audio properties and effects,
|
||||||
|
* loads all the audio buffers for sounds belonging to sprites, and creates a single instrument player
|
||||||
|
* and a drum player, used by all play note and play drum blocks.
|
||||||
|
* @constructor
|
||||||
|
*/
|
||||||
|
const AudioEngine = function () {
|
||||||
|
|
||||||
|
// create the global audio effects
|
||||||
|
this.roboticEffect = new RoboticEffect();
|
||||||
|
this.fuzzEffect = new FuzzEffect();
|
||||||
|
this.echoEffect = new EchoEffect();
|
||||||
|
this.reverbEffect = new ReverbEffect();
|
||||||
|
|
||||||
|
// chain the global effects to the output
|
||||||
|
this.input = new Tone.Gain();
|
||||||
|
this.input.chain(
|
||||||
|
this.roboticEffect, this.fuzzEffect, this.echoEffect, this.reverbEffect,
|
||||||
|
Tone.Master
|
||||||
|
);
|
||||||
|
|
||||||
|
// global tempo in bpm (beats per minute)
|
||||||
|
this.currentTempo = 60;
|
||||||
|
|
||||||
|
// instrument player for play note blocks
|
||||||
|
this.instrumentPlayer = new InstrumentPlayer(this.input);
|
||||||
|
this.numInstruments = this.instrumentPlayer.instrumentNames.length;
|
||||||
|
|
||||||
|
// drum player for play drum blocks
|
||||||
|
this.drumPlayer = new DrumPlayer(this.input);
|
||||||
|
this.numDrums = this.drumPlayer.drumSounds.length;
|
||||||
|
|
||||||
|
// a map of md5s to audio buffers, holding sounds for all sprites
|
||||||
|
this.audioBuffers = {};
|
||||||
|
|
||||||
|
// microphone, for measuring loudness, with a level meter analyzer
|
||||||
|
this.mic = null;
|
||||||
|
this.micMeter = null;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Decode a sound, decompressing it into audio samples.
|
||||||
|
* Store a reference to it the sound in the audioBuffers dictionary, indexed by md5
|
||||||
|
* @param {object} sound - an object containing audio data and metadata for a sound
|
||||||
|
* @property {Buffer} data - sound data loaded from scratch-storage.
|
||||||
|
* @property {string} format - format type, either empty or adpcm.
|
||||||
|
* @property {string} md5 - the MD5 and extension of the sound.
|
||||||
|
* @returns {?Promise} - a promise which will resolve after the audio buffer is stored, or null on error.
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.decodeSound = function (sound) {
|
||||||
|
|
||||||
|
let loaderPromise = null;
|
||||||
|
|
||||||
|
switch (sound.format) {
|
||||||
|
case '':
|
||||||
|
loaderPromise = Tone.context.decodeAudioData(sound.data.buffer);
|
||||||
|
break;
|
||||||
|
case 'adpcm':
|
||||||
|
loaderPromise = (new ADPCMSoundDecoder()).decode(sound.data.buffer);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
return log.warn('unknown sound format', sound.format);
|
||||||
|
}
|
||||||
|
|
||||||
|
const storedContext = this;
|
||||||
|
return loaderPromise.then(
|
||||||
|
decodedAudio => {
|
||||||
|
storedContext.audioBuffers[sound.md5] = new Tone.Buffer(decodedAudio);
|
||||||
|
},
|
||||||
|
error => {
|
||||||
|
log.warn('audio data could not be decoded', error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An older version of the AudioEngine had this function to load all sounds
|
||||||
|
* This is a stub to provide a warning when it is called
|
||||||
|
* @todo remove this
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.loadSounds = function () {
|
||||||
|
log.warn('The loadSounds function is no longer available. Please use Scratch Storage.');
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Play a note for a duration on an instrument with a volume
|
||||||
|
* @param {number} note - a MIDI note number
|
||||||
|
* @param {number} beats - a duration in beats
|
||||||
|
* @param {number} inst - an instrument number (0-indexed)
|
||||||
|
* @param {number} vol - a volume level (0-100%)
|
||||||
|
* @return {Promise} a Promise that resolves after the duration has elapsed
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.playNoteForBeatsWithInstAndVol = function (note, beats, inst, vol) {
|
||||||
|
const sec = this.beatsToSec(beats);
|
||||||
|
this.instrumentPlayer.playNoteForSecWithInstAndVol(note, sec, inst, vol);
|
||||||
|
return this.waitForBeats(beats);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Convert a number of beats to a number of seconds, using the current tempo
|
||||||
|
* @param {number} beats number of beats to convert to secs
|
||||||
|
* @return {number} seconds number of seconds `beats` will last
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.beatsToSec = function (beats) {
|
||||||
|
return (60 / this.currentTempo) * beats;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wait for some number of beats
|
||||||
|
* @param {number} beats number of beats to wait for
|
||||||
|
* @return {Promise} a Promise that resolves after the duration has elapsed
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.waitForBeats = function (beats) {
|
||||||
|
const storedContext = this;
|
||||||
|
return new Promise(resolve => {
|
||||||
|
setTimeout(() => {
|
||||||
|
resolve();
|
||||||
|
}, storedContext.beatsToSec(beats) * 1000);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the global tempo in bpm (beats per minute)
|
||||||
|
* @param {number} value - the new tempo to set
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.setTempo = function (value) {
|
||||||
|
this.currentTempo = value;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Change the tempo by some number of bpm (beats per minute)
|
||||||
|
* @param {number} value - the number of bpm to change the tempo by
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.changeTempo = function (value) {
|
||||||
|
this.setTempo(this.currentTempo + value);
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the current loudness of sound received by the microphone.
|
||||||
|
* Sound is measured in RMS and smoothed.
|
||||||
|
* @return {number} loudness scaled 0 to 100
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.getLoudness = function () {
|
||||||
|
if (!this.mic) {
|
||||||
|
this.mic = new Tone.UserMedia();
|
||||||
|
this.micMeter = new Tone.Meter('level', 0.5);
|
||||||
|
this.mic.open();
|
||||||
|
this.mic.connect(this.micMeter);
|
||||||
|
}
|
||||||
|
if (this.mic && this.mic.state === 'started') {
|
||||||
|
return this.micMeter.value * 100;
|
||||||
|
}
|
||||||
|
return -1;
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Names of the audio effects.
|
||||||
|
* @readonly
|
||||||
|
* @enum {string}
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.EFFECT_NAMES = {
|
||||||
|
pitch: 'pitch',
|
||||||
|
pan: 'pan',
|
||||||
|
echo: 'echo',
|
||||||
|
reverb: 'reverb',
|
||||||
|
fuzz: 'fuzz',
|
||||||
|
robot: 'robot'
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an AudioPlayer. Each sprite or clone has an AudioPlayer.
|
||||||
|
* It includes a reference to the AudioEngine so it can use global
|
||||||
|
* functionality such as playing notes.
|
||||||
|
* @return {AudioPlayer} new AudioPlayer instance
|
||||||
|
*/
|
||||||
|
AudioEngine.prototype.createPlayer = function () {
|
||||||
|
return new AudioPlayer(this);
|
||||||
|
};
|
||||||
|
|
||||||
module.exports = AudioEngine;
|
module.exports = AudioEngine;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
var minilog = require('minilog');
|
const minilog = require('minilog');
|
||||||
minilog.enable();
|
minilog.enable();
|
||||||
|
|
||||||
module.exports = minilog('scratch-audioengine');
|
module.exports = minilog('scratch-audioengine');
|
||||||
|
|
|
@ -2,7 +2,7 @@ var path = require('path');
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
entry: {
|
entry: {
|
||||||
'dist': './src/index.js'
|
dist: './src/index.js'
|
||||||
},
|
},
|
||||||
output: {
|
output: {
|
||||||
path: __dirname,
|
path: __dirname,
|
||||||
|
|
Loading…
Reference in a new issue