scratch-audioengine
Advanced tools
Comparing version 0.1.0-prerelease.1479334367 to 0.1.0-prerelease.1479583259
{ | ||
"name": "scratch-audioengine", | ||
"version": "0.1.0-prerelease.1479334367", | ||
"version": "0.1.0-prerelease.1479583259", | ||
"description": "audio engine for scratch 3.0", | ||
@@ -5,0 +5,0 @@ "main": "dist.js", |
@@ -8,2 +8,4 @@ /* | ||
to do: I think this will ultimately need to run in a web worker | ||
*/ | ||
@@ -15,67 +17,77 @@ | ||
function ADPCMSoundLoader (url) { | ||
var request = new XMLHttpRequest(); | ||
request.open('GET', url, true); | ||
request.responseType = 'arraybuffer'; | ||
function ADPCMSoundLoader () { | ||
} | ||
request.onload = function () { | ||
var audioData = request.response; | ||
var stream = new ArrayBufferStream(audioData); | ||
ADPCMSoundLoader.prototype.load = function (url) { | ||
var riffStr = stream.readUint8String(4); | ||
if (riffStr != 'RIFF') { | ||
log.warn('incorrect adpcm wav header'); | ||
} | ||
return new Promise(function (resolve, reject) { | ||
var lengthInHeader = stream.readInt32(); | ||
if ((lengthInHeader + 8) != audioData.byteLength) { | ||
log.warn('adpcm wav length in header: ' + length + 'is incorrect'); | ||
} | ||
var request = new XMLHttpRequest(); | ||
request.open('GET', url, true); | ||
request.responseType = 'arraybuffer'; | ||
var wavStr = stream.readUint8String(4); | ||
if (wavStr != 'WAVE') { | ||
log.warn('incorrect adpcm wav header'); | ||
} | ||
request.onload = function () { | ||
var audioData = request.response; | ||
var stream = new ArrayBufferStream(audioData); | ||
var formatChunk = this.extractChunk('fmt ', stream); | ||
this.encoding = formatChunk.readUint16(); | ||
this.channels = formatChunk.readUint16(); | ||
this.samplesPerSecond = formatChunk.readUint32(); | ||
this.bytesPerSecond = formatChunk.readUint32(); | ||
this.blockAlignment = formatChunk.readUint16(); | ||
this.bitsPerSample = formatChunk.readUint16(); | ||
formatChunk.position += 2; // skip extra header byte count | ||
this.samplesPerBlock = formatChunk.readUint16(); | ||
this.adpcmBlockSize = ((this.samplesPerBlock - 1) / 2) + 4; // block size in bytes | ||
var riffStr = stream.readUint8String(4); | ||
if (riffStr != 'RIFF') { | ||
log.warn('incorrect adpcm wav header'); | ||
reject(); | ||
} | ||
var samples = this.imaDecompress(this.extractChunk('data', stream), this.adpcmBlockSize); | ||
var buffer = Tone.context.createBuffer(1, samples.length, this.samplesPerSecond); | ||
var lengthInHeader = stream.readInt32(); | ||
if ((lengthInHeader + 8) != audioData.byteLength) { | ||
log.warn('adpcm wav length in header: ' + lengthInHeader + ' is incorrect'); | ||
} | ||
// todo: optimize this? | ||
for (var i=0; i<samples.length; i++) { | ||
buffer.getChannelData(0)[i] = samples[i] / 32768; | ||
} | ||
var wavStr = stream.readUint8String(4); | ||
if (wavStr != 'WAVE') { | ||
log.warn('incorrect adpcm wav header'); | ||
reject(); | ||
} | ||
var source = Tone.context.createBufferSource(); | ||
source.buffer = buffer; | ||
source.connect(Tone.Master); | ||
source.start(); | ||
var formatChunk = this.extractChunk('fmt ', stream); | ||
this.encoding = formatChunk.readUint16(); | ||
this.channels = formatChunk.readUint16(); | ||
this.samplesPerSecond = formatChunk.readUint32(); | ||
this.bytesPerSecond = formatChunk.readUint32(); | ||
this.blockAlignment = formatChunk.readUint16(); | ||
this.bitsPerSample = formatChunk.readUint16(); | ||
formatChunk.position += 2; // skip extra header byte count | ||
this.samplesPerBlock = formatChunk.readUint16(); | ||
this.adpcmBlockSize = ((this.samplesPerBlock - 1) / 2) + 4; // block size in bytes | ||
var samples = this.imaDecompress(this.extractChunk('data', stream), this.adpcmBlockSize); | ||
}.bind(this); | ||
request.send(); | ||
// 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); | ||
this.stepTable = [ | ||
7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, | ||
50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, | ||
253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, | ||
1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, | ||
3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, | ||
12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767]; | ||
// todo: optimize this? e.g. replace the divide by storing 1/32768 and multiply? | ||
for (var i=0; i<samples.length; i++) { | ||
buffer.getChannelData(0)[i] = samples[i] / 32768; | ||
} | ||
this.indexTable = [ | ||
-1, -1, -1, -1, 2, 4, 6, 8, | ||
-1, -1, -1, -1, 2, 4, 6, 8]; | ||
} | ||
resolve(buffer); | ||
}.bind(this); | ||
request.send(); | ||
}.bind(this)); | ||
}; | ||
ADPCMSoundLoader.prototype.stepTable = [ | ||
7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31, 34, 37, 41, 45, | ||
50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143, 157, 173, 190, 209, 230, | ||
253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658, 724, 796, 876, 963, | ||
1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024, 3327, | ||
3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, | ||
12635, 13899, 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767]; | ||
ADPCMSoundLoader.prototype.indexTable = [ | ||
-1, -1, -1, -1, 2, 4, 6, 8, | ||
-1, -1, -1, -1, 2, 4, 6, 8]; | ||
ADPCMSoundLoader.prototype.extractChunk = function (chunkType, stream) { | ||
@@ -82,0 +94,0 @@ stream.position = 12; |
123
src/index.js
@@ -23,2 +23,15 @@ var log = require('./log'); | ||
this.wobble = new Tone.Effect(); | ||
var wobbleLFO = new Tone.LFO(10, 0, 1).start(); | ||
var wobbleGain = new Tone.Gain(); | ||
wobbleLFO.connect(wobbleGain.gain); | ||
this.wobble.effectSend.chain(wobbleGain, this.wobble.effectReturn); | ||
// telephone effect - simulating the 'tinny' sound coming over a phone line | ||
// basically, a lowpass filter and a highpass filter | ||
this.telephone = new Tone.Effect(); | ||
var telephoneLP = new Tone.Filter(1200, 'lowpass', -24); | ||
var telephoneHP = new Tone.Filter(800, 'highpass', -24); | ||
this.telephone.effectSend.chain(telephoneLP, telephoneHP, this.telephone.effectReturn); | ||
// the effects are chained to an effects node for this clone, then to the master output | ||
@@ -28,3 +41,6 @@ // so audio is sent from each player or instrument, through the effects in order, then out | ||
this.effectsNode = new Tone.Gain(); | ||
this.effectsNode.chain(this.vocoder, this.distortion, this.delay, this.panner, this.reverb, Tone.Master); | ||
this.effectsNode.chain( | ||
// this.vocoder, | ||
this.distortion, this.delay, this.telephone, | ||
this.wobble, this.panner, this.reverb, Tone.Master); | ||
@@ -36,3 +52,4 @@ // reset effects to their default parameters | ||
this.soundPlayers = this.loadSounds(sounds); | ||
this.soundPlayers = []; | ||
this.loadSounds(sounds); | ||
// Tone.Buffer.on('load', this._soundsLoaded.bind(this)); | ||
@@ -69,34 +86,39 @@ | ||
AudioEngine.prototype.loadSounds = function (sounds) { | ||
var soundPlayers = []; | ||
for (var i=0; i<sounds.length; i++) { | ||
this.soundPlayers = []; | ||
var buffer; | ||
// create a set of empty sound player objects | ||
// the sound buffers will be added asynchronously as they load | ||
for (var i=0; i<sounds.length; i++){ | ||
var player = {}; | ||
player.buffer = null; | ||
player.bufferSource = null; | ||
this.soundPlayers[i] = player; | ||
} | ||
if (sounds[i].format == 'adpcm') { | ||
// load the sounds- most sounds decode natively, but for adpcm sounds | ||
// we use our own decoder | ||
var storedContext = this; | ||
for (var index=0; index<sounds.length; index++) { | ||
if (sounds[index].format == 'adpcm') { | ||
log.warn('attempting to load sound in adpcm format'); | ||
var loader = new ADPCMSoundLoader(sounds[i].fileUrl); | ||
loader; // lint | ||
// var audioBuffer = loader.getAudioBuffer(); | ||
// buffer = new Tone.Buffer(audioBuffer); | ||
// create a closure to the sound index, to use when the | ||
// docder completes and resolves the promise | ||
(function () { | ||
var storedIndex = index; | ||
var loader = new ADPCMSoundLoader(); | ||
loader.load(sounds[storedIndex].fileUrl).then(function (audioBuffer) { | ||
storedContext.soundPlayers[storedIndex].buffer = new Tone.Buffer(audioBuffer); | ||
}); | ||
}()); | ||
} else { | ||
buffer = new Tone.Buffer(sounds[i].fileUrl); | ||
this.soundPlayers[index].buffer = new Tone.Buffer(sounds[index].fileUrl); | ||
} | ||
var player = {}; | ||
player.buffer = buffer; | ||
player.bufferSource = null; | ||
soundPlayers[i] = player; | ||
} | ||
return soundPlayers; | ||
}; | ||
// AudioEngine.prototype._soundsLoaded = function() { | ||
// console.log('all sounds loaded'); | ||
// } | ||
AudioEngine.prototype.playSound = function (index) { | ||
// if the soundplayer exists and its buffer has loaded | ||
if (this.soundPlayers[index] && this.soundPlayers[index].buffer.loaded) { | ||
if (this.soundPlayers[index].buffer && this.soundPlayers[index].buffer.loaded) { | ||
// stop the sound if it's already playing | ||
@@ -115,2 +137,4 @@ var b = this.soundPlayers[index].bufferSource; | ||
return new Promise(function (resolve) { | ||
// bufferSource.onended = resolve; // this works, but causes the block to display a | ||
// blocklydropdowncontent that says 'BufferSource' | ||
bufferSource.onended = function (){resolve();}; | ||
@@ -199,4 +223,4 @@ }); | ||
switch (effect) { | ||
case 'ECHO': | ||
this.delay.wet.value = (value / 100) / 2; // max 50% wet | ||
case 'PITCH': | ||
this._setPitchShift(value); | ||
break; | ||
@@ -206,11 +230,17 @@ case 'PAN': | ||
break; | ||
case 'ECHO': | ||
this.delay.wet.value = (value / 100) / 2; // max 50% wet | ||
break; | ||
case 'REVERB': | ||
this.reverb.wet.value = value / 100; | ||
break; | ||
case 'PITCH': | ||
this._setPitchShift(value); | ||
break; | ||
case 'FUZZ' : | ||
this.distortion.wet.value = value / 100; | ||
break; | ||
case 'TELEPHONE' : | ||
this.telephone.wet.value = value / 100; | ||
break; | ||
case 'WOBBLE' : | ||
this.wobble.wet.value = value / 100; | ||
break; | ||
case 'ROBOTIC' : | ||
@@ -224,5 +254,4 @@ this.vocoder.wet.value = value / 100; | ||
switch (effect) { | ||
case 'ECHO': | ||
this.delay.wet.value += (value / 100) / 2; // max 50% wet | ||
this.delay.wet.value = this._clamp(this.delay.wet.value, 0, 0.5); | ||
case 'PITCH': | ||
this._setPitchShift(this.pitchEffectValue + Number(value)); | ||
break; | ||
@@ -233,2 +262,6 @@ case 'PAN': | ||
break; | ||
case 'ECHO': | ||
this.delay.wet.value += (value / 100) / 2; // max 50% wet | ||
this.delay.wet.value = this._clamp(this.delay.wet.value, 0, 0.5); | ||
break; | ||
case 'REVERB': | ||
@@ -238,5 +271,2 @@ this.reverb.wet.value += value / 100; | ||
break; | ||
case 'PITCH': | ||
this._setPitchShift(this.pitchEffectValue + Number(value)); | ||
break; | ||
case 'FUZZ' : | ||
@@ -246,2 +276,10 @@ this.distortion.wet.value += value / 100; | ||
break; | ||
case 'TELEPHONE' : | ||
this.telephone.wet.value += value / 100; | ||
this.telephone.wet.value = this._clamp(this.telephone.wet.value, 0, 1); | ||
break; | ||
case 'WOBBLE' : | ||
this.wobble.wet.value += value / 100; | ||
this.wobble.wet.value = this._clamp(this.wobble.wet.value, 0, 1); | ||
break; | ||
case 'ROBOTIC' : | ||
@@ -257,16 +295,21 @@ this.vocoder.wet.value += value / 100; | ||
this.pitchEffectValue = value; | ||
var freq = this._getPitchRatio() * Tone.Frequency('C3').eval(); | ||
this.vocoder.setCarrierOscFrequency(freq); | ||
if (!this.soundPlayers) { | ||
return; | ||
} | ||
var ratio = this._getPitchRatio(); | ||
this._setPlaybackRateForAllSoundPlayers(ratio); | ||
var ratio = this._getPitchRatio(); | ||
}; | ||
AudioEngine.prototype._setPlaybackRateForAllSoundPlayers = function (rate) { | ||
for (var i=0; i<this.soundPlayers.length; i++) { | ||
var s = this.soundPlayers[i].bufferSource; | ||
if (s && s.playbackRate) { | ||
s.playbackRate.value = ratio; | ||
s.playbackRate.value = rate; | ||
} | ||
} | ||
var freq = this._getPitchRatio() * Tone.Frequency('C3').eval(); | ||
this.vocoder.setCarrierOscFrequency(freq); | ||
}; | ||
@@ -292,3 +335,2 @@ | ||
this.delay.wet.value = 0; | ||
this._setPitchShift(0); | ||
this.panner.pan.value = 0; | ||
@@ -298,2 +340,5 @@ this.reverb.wet.value = 0; | ||
this.vocoder.wet.value = 0; | ||
this.wobble.wet.value = 0; | ||
this.telephone.wet.value = 0; | ||
this._setPitchShift(0); | ||
@@ -300,0 +345,0 @@ this.effectsNode.gain.value = 1; |
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
814469
23494