scratch-audio
Advanced tools
Comparing version 0.1.0-prerelease.1497907229 to 0.1.0-prerelease.1498238771
{ | ||
"name": "scratch-audio", | ||
"version": "0.1.0-prerelease.1497907229", | ||
"version": "0.1.0-prerelease.1498238771", | ||
"description": "audio engine for scratch 3.0", | ||
@@ -33,3 +33,2 @@ "main": "dist.js", | ||
"soundfont-player": "0.10.5", | ||
"tone": "0.9.0", | ||
"travis-after-all": "^1.4.4", | ||
@@ -36,0 +35,0 @@ "webpack": "2.4.0" |
const ArrayBufferStream = require('./ArrayBufferStream'); | ||
const Tone = require('tone'); | ||
const log = require('./log'); | ||
@@ -14,2 +13,9 @@ | ||
/** | ||
* @param {AudioContext} audioContext - a webAudio context | ||
* @constructor | ||
*/ | ||
constructor (audioContext) { | ||
this.audioContext = audioContext; | ||
} | ||
/** | ||
* Data used by the decompression algorithm | ||
@@ -44,3 +50,3 @@ * @type {Array} | ||
* @param {ArrayBuffer} audioData - containing ADPCM encoded wav audio | ||
* @return {Tone.Buffer} the decoded audio buffer | ||
* @return {AudioBuffer} the decoded audio buffer | ||
*/ | ||
@@ -82,4 +88,3 @@ decode (audioData) { | ||
// @todo this line is the only place Tone is used here, should be possible to remove | ||
const buffer = Tone.context.createBuffer(1, samples.length, this.samplesPerSecond); | ||
const buffer = this.audioContext.createBuffer(1, samples.length, this.samplesPerSecond); | ||
@@ -86,0 +91,0 @@ // @todo optimize this? e.g. replace the divide by storing 1/32768 and multiply? |
const SoundPlayer = require('./SoundPlayer'); | ||
const Tone = require('tone'); | ||
@@ -7,7 +6,7 @@ class DrumPlayer { | ||
* 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 {AudioContext} audioContext - a webAudio context | ||
* @constructor | ||
*/ | ||
constructor (outputNode) { | ||
this.outputNode = outputNode; | ||
constructor (audioContext) { | ||
this.audioContext = audioContext; | ||
@@ -39,5 +38,17 @@ const baseUrl = 'https://raw.githubusercontent.com/LLK/scratch-audio/develop/sound-files/drums/'; | ||
for (let i = 0; i < fileNames.length; i++) { | ||
const url = `${baseUrl + fileNames[i]}_22k.wav`; | ||
this.drumSounds[i] = new SoundPlayer(this.outputNode); | ||
this.drumSounds[i].setBuffer(new Tone.Buffer(url)); | ||
this.drumSounds[i] = new SoundPlayer(this.audioContext); | ||
// download and decode the drum sounds | ||
// @todo: use scratch-storage to manage these sound files | ||
const url = `${baseUrl}${fileNames[i]}_22k.wav`; | ||
const request = new XMLHttpRequest(); | ||
request.open('GET', url, true); | ||
request.responseType = 'arraybuffer'; | ||
request.onload = () => { | ||
const audioData = request.response; | ||
this.audioContext.decodeAudioData(audioData).then(buffer => { | ||
this.drumSounds[i].setBuffer(buffer); | ||
}); | ||
}; | ||
request.send(); | ||
} | ||
@@ -51,6 +62,6 @@ } | ||
* @param {number} drum - the drum number to play (0-indexed) | ||
* @param {Tone.Gain} outputNode - a node to send the output to | ||
* @param {AudioNode} outputNode - a node to send the output to | ||
*/ | ||
play (drum, outputNode) { | ||
this.drumSounds[drum].outputNode = outputNode; | ||
this.drumSounds[drum].connect(outputNode); | ||
this.drumSounds[drum].start(); | ||
@@ -57,0 +68,0 @@ } |
@@ -1,3 +0,1 @@ | ||
const Tone = require('tone'); | ||
/** | ||
@@ -9,8 +7,11 @@ * A pan effect, which moves the sound to the left or right between the speakers | ||
*/ | ||
class PanEffect extends Tone.Effect { | ||
constructor () { | ||
super(); | ||
class PanEffect { | ||
/** | ||
* @param {AudioContext} audioContext - a webAudio context | ||
* @constructor | ||
*/ | ||
constructor (audioContext) { | ||
this.audioContext = audioContext; | ||
this.panner = this.audioContext.createStereoPanner(); | ||
this.value = 0; | ||
this.panner = new Tone.Panner(); | ||
this.effectSend.chain(this.panner, this.effectReturn); | ||
} | ||
@@ -27,2 +28,6 @@ | ||
connect (node) { | ||
this.panner.connect(node); | ||
} | ||
/** | ||
@@ -29,0 +34,0 @@ * Change the effect value |
@@ -1,3 +0,1 @@ | ||
const Tone = require('tone'); | ||
/** | ||
@@ -24,3 +22,2 @@ * A pitch change effect, which changes the playback rate of the sound in order | ||
this.ratio = 1; // the playback rate ratio | ||
this.tone = new Tone(); | ||
} | ||
@@ -56,3 +53,5 @@ | ||
getRatio (val) { | ||
return this.tone.intervalToFrequencyRatio(val / 10); | ||
const interval = val / 10; | ||
// Convert the musical interval in semitones to a frequency ratio | ||
return Math.pow(2, (interval / 12)); | ||
} | ||
@@ -59,0 +58,0 @@ |
const log = require('./log'); | ||
const Tone = require('tone'); | ||
@@ -28,11 +27,11 @@ const PitchEffect = require('./effects/PitchEffect'); | ||
// effects setup | ||
// Create the audio effects | ||
this.pitchEffect = new PitchEffect(); | ||
this.panEffect = new PanEffect(); | ||
this.panEffect = new PanEffect(this.audioEngine.audioContext); | ||
// the effects are chained to an effects node for this player, then to the main audio engine | ||
// audio is sent from each soundplayer, through the effects in order, then to the global effects | ||
// note that the pitch effect works differently - it sets the playback rate for each soundplayer | ||
this.effectsNode = new Tone.Gain(); | ||
this.effectsNode.chain(this.panEffect, this.audioEngine.input); | ||
// Chain the audio effects together | ||
// effectsNode -> panEffect -> audioEngine.input | ||
this.effectsNode = this.audioEngine.audioContext.createGain(); | ||
this.effectsNode.connect(this.panEffect.panner); | ||
this.panEffect.connect(this.audioEngine.input); | ||
@@ -63,3 +62,3 @@ // reset effects to their default parameters | ||
// create a new soundplayer to play the sound | ||
const player = new SoundPlayer(); | ||
const player = new SoundPlayer(this.audioEngine.audioContext); | ||
player.setBuffer(this.audioEngine.audioBuffers[md5]); | ||
@@ -155,5 +154,8 @@ player.connect(this.effectsNode); | ||
constructor () { | ||
this.input = new Tone.Gain(); | ||
this.input.connect(Tone.Master); | ||
const AudioContext = window.AudioContext || window.webkitAudioContext; | ||
this.audioContext = new AudioContext(); | ||
this.input = this.audioContext.createGain(); | ||
this.input.connect(this.audioContext.destination); | ||
// global tempo in bpm (beats per minute) | ||
@@ -163,7 +165,8 @@ this.currentTempo = 60; | ||
// instrument player for play note blocks | ||
this.instrumentPlayer = new InstrumentPlayer(this.input); | ||
this.instrumentPlayer = new InstrumentPlayer(this.audioContext); | ||
this.instrumentPlayer.outputNode = this.input; | ||
this.numInstruments = this.instrumentPlayer.instrumentNames.length; | ||
// drum player for play drum blocks | ||
this.drumPlayer = new DrumPlayer(this.input); | ||
this.drumPlayer = new DrumPlayer(this.audioContext); | ||
this.numDrums = this.drumPlayer.drumSounds.length; | ||
@@ -176,3 +179,2 @@ | ||
this.mic = null; | ||
this.micMeter = null; | ||
} | ||
@@ -205,10 +207,10 @@ | ||
// Make a copy of the buffer because decoding detaches the original buffer | ||
var bufferCopy = sound.data.buffer.slice(0); | ||
const bufferCopy = sound.data.buffer.slice(0); | ||
switch (sound.format) { | ||
case '': | ||
loaderPromise = Tone.context.decodeAudioData(bufferCopy); | ||
loaderPromise = this.audioContext.decodeAudioData(bufferCopy); | ||
break; | ||
case 'adpcm': | ||
loaderPromise = (new ADPCMSoundDecoder()).decode(bufferCopy); | ||
loaderPromise = (new ADPCMSoundDecoder(this.audioContext)).decode(bufferCopy); | ||
break; | ||
@@ -222,3 +224,3 @@ default: | ||
decodedAudio => { | ||
storedContext.audioBuffers[sound.md5] = new Tone.Buffer(decodedAudio); | ||
storedContext.audioBuffers[sound.md5] = decodedAudio; | ||
}, | ||
@@ -296,16 +298,47 @@ error => { | ||
* Sound is measured in RMS and smoothed. | ||
* Some code adapted from Tone.js: https://github.com/Tonejs/Tone.js | ||
* @return {number} loudness scaled 0 to 100 | ||
*/ | ||
getLoudness () { | ||
if (!this.mic) { | ||
this.mic = new Tone.UserMedia(); | ||
this.micMeter = new Tone.Meter('level', 0.5); | ||
this.mic.open(); | ||
this.mic.connect(this.micMeter); | ||
// The microphone has not been set up, so try to connect to it | ||
if (!this.mic && !this.connectingToMic) { | ||
this.connectingToMic = true; // prevent multiple connection attempts | ||
navigator.mediaDevices.getUserMedia({audio: true}).then(stream => { | ||
this.mic = this.audioContext.createMediaStreamSource(stream); | ||
this.analyser = this.audioContext.createAnalyser(); | ||
this.mic.connect(this.analyser); | ||
this.micDataArray = new Float32Array(this.analyser.fftSize); | ||
}) | ||
.catch(err => { | ||
log.warn(err); | ||
}); | ||
} | ||
if (this.mic && this.mic.state === 'started') { | ||
return this.micMeter.value * 100; | ||
// If the microphone is set up and active, measure the loudness | ||
if (this.mic && this.mic.mediaStream.active) { | ||
this.analyser.getFloatTimeDomainData(this.micDataArray); | ||
let sum = 0; | ||
// compute the RMS of the sound | ||
for (let i = 0; i < this.micDataArray.length; i++){ | ||
sum += Math.pow(this.micDataArray[i], 2); | ||
} | ||
let rms = Math.sqrt(sum / this.micDataArray.length); | ||
// smooth the value, if it is descending | ||
if (this._lastValue) { | ||
rms = Math.max(rms, this._lastValue * 0.6); | ||
} | ||
this._lastValue = rms; | ||
// Scale the measurement so it's more sensitive to quieter sounds | ||
rms *= 1.63; | ||
rms = Math.sqrt(rms); | ||
// Scale it up to 0-100 and round | ||
rms = Math.round(rms * 100); | ||
// Prevent it from going above 100 | ||
rms = Math.min(rms, 100); | ||
return rms; | ||
} | ||
// if there is no microphone input, return -1 | ||
return -1; | ||
} | ||
@@ -312,0 +345,0 @@ |
@@ -1,2 +0,1 @@ | ||
const Tone = require('tone'); | ||
const Soundfont = require('soundfont-player'); | ||
@@ -13,7 +12,8 @@ | ||
* duration, or run it through the sprite-specific audio effects. | ||
* @param {Tone.Gain} outputNode - a webAudio node that the instrument will send its output to | ||
* @param {AudioContext} audioContext - a webAudio context | ||
* @constructor | ||
*/ | ||
constructor (outputNode) { | ||
this.outputNode = outputNode; | ||
constructor (audioContext) { | ||
this.audioContext = audioContext; | ||
this.outputNode = null; | ||
@@ -46,3 +46,3 @@ // Instrument names used by Musyng Kite soundfont, in order to | ||
this.instruments[instrumentNum].play( | ||
note, Tone.context.currentTime, { | ||
note, this.audioContext.currentTime, { | ||
duration: sec, | ||
@@ -64,3 +64,3 @@ gain: gain | ||
} | ||
return Soundfont.instrument(Tone.context, this.instrumentNames[instrumentNum]) | ||
return Soundfont.instrument(this.audioContext, this.instrumentNames[instrumentNum]) | ||
.then(inst => { | ||
@@ -67,0 +67,0 @@ inst.connect(this.outputNode); |
@@ -1,2 +0,1 @@ | ||
const Tone = require('tone'); | ||
const log = require('./log'); | ||
@@ -8,5 +7,10 @@ | ||
class SoundPlayer { | ||
constructor () { | ||
/** | ||
* @param {AudioContext} audioContext - a webAudio context | ||
* @constructor | ||
*/ | ||
constructor (audioContext) { | ||
this.audioContext = audioContext; | ||
this.outputNode = null; | ||
this.buffer = new Tone.Buffer(); | ||
this.buffer = null; | ||
this.bufferSource = null; | ||
@@ -19,3 +23,3 @@ this.playbackRate = 1; | ||
* Connect the SoundPlayer to an output node | ||
* @param {Tone.Gain} node - an output node to connect to | ||
* @param {GainNode} node - an output node to connect to | ||
*/ | ||
@@ -28,3 +32,3 @@ connect (node) { | ||
* Set an audio buffer | ||
* @param {Tone.Buffer} buffer Buffer to set | ||
* @param {AudioBuffer} buffer - Buffer to set | ||
*/ | ||
@@ -61,3 +65,3 @@ setBuffer (buffer) { | ||
start () { | ||
if (!this.buffer || !this.buffer.loaded) { | ||
if (!this.buffer) { | ||
log.warn('tried to play a sound that was not loaded yet'); | ||
@@ -67,4 +71,4 @@ return; | ||
this.bufferSource = Tone.context.createBufferSource(); | ||
this.bufferSource.buffer = this.buffer.get(); | ||
this.bufferSource = this.audioContext.createBufferSource(); | ||
this.bufferSource.buffer = this.buffer; | ||
this.bufferSource.playbackRate.value = this.playbackRate; | ||
@@ -83,8 +87,7 @@ this.bufferSource.connect(this.outputNode); | ||
finished () { | ||
const storedContext = this; | ||
return new Promise(resolve => { | ||
storedContext.bufferSource.onended = function () { | ||
this.bufferSource.onended = () => { | ||
this.isPlaying = false; | ||
resolve(); | ||
}.bind(storedContext); | ||
}; | ||
}); | ||
@@ -91,0 +94,0 @@ } |
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
11
592715
3780
2