mirror of
https://github.com/scratchfoundation/scratch-audio.git
synced 2024-12-22 14:02:29 -05:00
make objects for each effect
This commit is contained in:
parent
f797ec80f3
commit
9120c3d430
9 changed files with 369 additions and 264 deletions
52
src/effects/EchoEffect.js
Normal file
52
src/effects/EchoEffect.js
Normal file
|
@ -0,0 +1,52 @@
|
|||
/*
|
||||
|
||||
An echo effect
|
||||
|
||||
0 mutes the effect
|
||||
Values up to 100 set the echo feedback amount,
|
||||
increasing the time it takes the echo to fade away
|
||||
|
||||
Clamped 0-100
|
||||
|
||||
*/
|
||||
|
||||
var Tone = require('tone');
|
||||
|
||||
function EchoEffect () {
|
||||
Tone.Effect.call(this);
|
||||
|
||||
this.value = 0;
|
||||
|
||||
this.delay = new Tone.FeedbackDelay(0.25, 0.5);
|
||||
|
||||
this.effectSend.chain(this.delay, this.effectReturn);
|
||||
}
|
||||
|
||||
Tone.extend(EchoEffect, Tone.Effect);
|
||||
|
||||
EchoEffect.prototype.set = function (val) {
|
||||
this.value = val;
|
||||
|
||||
this.value = this.clamp(this.value, 0, 100);
|
||||
|
||||
// mute the effect if value is 0
|
||||
if (this.value == 0) {
|
||||
this.wet.value = 0;
|
||||
} else {
|
||||
this.wet.value = 1;
|
||||
}
|
||||
|
||||
var feedback = (this.value / 100) * 0.75;
|
||||
this.delay.feedback.rampTo(feedback, 1/60);
|
||||
};
|
||||
|
||||
EchoEffect.prototype.changeBy = function (val) {
|
||||
this.set(this.value + val);
|
||||
};
|
||||
|
||||
EchoEffect.prototype.clamp = function (input, min, max) {
|
||||
return Math.min(Math.max(input, min), max);
|
||||
};
|
||||
|
||||
module.exports = EchoEffect;
|
||||
|
44
src/effects/FuzzEffect.js
Normal file
44
src/effects/FuzzEffect.js
Normal file
|
@ -0,0 +1,44 @@
|
|||
/*
|
||||
|
||||
A fuzz effect
|
||||
|
||||
Distortion
|
||||
|
||||
the value controls the wet/dry amount
|
||||
|
||||
Clamped 0-100
|
||||
|
||||
*/
|
||||
|
||||
var Tone = require('tone');
|
||||
|
||||
function FuzzEffect () {
|
||||
Tone.Effect.call(this);
|
||||
|
||||
this.value = 0;
|
||||
|
||||
this.distortion = new Tone.Distortion(1);
|
||||
|
||||
this.effectSend.chain(this.distortion, this.effectReturn);
|
||||
}
|
||||
|
||||
Tone.extend(FuzzEffect, Tone.Effect);
|
||||
|
||||
FuzzEffect.prototype.set = function (val) {
|
||||
this.value = val;
|
||||
|
||||
this.value = this.clamp(this.value, 0, 100);
|
||||
|
||||
this.distortion.wet.value = this.value / 100;
|
||||
};
|
||||
|
||||
FuzzEffect.prototype.changeBy = function (val) {
|
||||
this.set(this.value + val);
|
||||
};
|
||||
|
||||
FuzzEffect.prototype.clamp = function (input, min, max) {
|
||||
return Math.min(Math.max(input, min), max);
|
||||
};
|
||||
|
||||
module.exports = FuzzEffect;
|
||||
|
42
src/effects/PanEffect.js
Normal file
42
src/effects/PanEffect.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
|
||||
A Pan effect
|
||||
|
||||
-100 puts the audio on the left channel, 0 centers it, 100 makes puts it on the right.
|
||||
|
||||
Clamped -100 to 100
|
||||
|
||||
*/
|
||||
|
||||
var Tone = require('tone');
|
||||
|
||||
function PanEffect () {
|
||||
Tone.Effect.call(this);
|
||||
|
||||
this.value = 0;
|
||||
|
||||
this.panner = new Tone.Panner();
|
||||
|
||||
this.effectSend.chain(this.panner, this.effectReturn);
|
||||
}
|
||||
|
||||
Tone.extend(PanEffect, Tone.Effect);
|
||||
|
||||
PanEffect.prototype.set = function (val) {
|
||||
this.value = val;
|
||||
|
||||
this.value = this.clamp(this.value, -100, 100);
|
||||
|
||||
this.panner.pan.value = this.value / 100;
|
||||
};
|
||||
|
||||
PanEffect.prototype.changeBy = function (val) {
|
||||
this.set(this.value + val);
|
||||
};
|
||||
|
||||
PanEffect.prototype.clamp = function (input, min, max) {
|
||||
return Math.min(Math.max(input, min), max);
|
||||
};
|
||||
|
||||
module.exports = PanEffect;
|
||||
|
39
src/effects/PitchEffect.js
Normal file
39
src/effects/PitchEffect.js
Normal file
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
|
||||
A Pitch effect
|
||||
|
||||
*/
|
||||
|
||||
var Tone = require('tone');
|
||||
|
||||
function PitchEffect () {
|
||||
this.value = 0;
|
||||
|
||||
this.tone = new Tone();
|
||||
}
|
||||
|
||||
PitchEffect.prototype.set = function (val, players) {
|
||||
this.value = val;
|
||||
this.updatePlayers(players);
|
||||
};
|
||||
|
||||
PitchEffect.prototype.changeBy = function (val, players) {
|
||||
this.set(this.value + val, players);
|
||||
};
|
||||
|
||||
PitchEffect.prototype.getRatio = function () {
|
||||
return this.tone.intervalToFrequencyRatio(this.value / 10);
|
||||
};
|
||||
|
||||
PitchEffect.prototype.updatePlayers = function (players) {
|
||||
if (!players) return;
|
||||
|
||||
var ratio = this.getRatio();
|
||||
for (var i=0; i<players.length; i++) {
|
||||
players[i].setPlaybackRate(ratio);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports = PitchEffect;
|
||||
|
42
src/effects/ReverbEffect.js
Normal file
42
src/effects/ReverbEffect.js
Normal file
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
|
||||
A Reverb effect
|
||||
|
||||
The value controls the wet/dry amount of the effect
|
||||
|
||||
Clamped 0 to 100
|
||||
|
||||
*/
|
||||
|
||||
var Tone = require('tone');
|
||||
|
||||
function ReverbEffect () {
|
||||
Tone.Effect.call(this);
|
||||
|
||||
this.value = 0;
|
||||
|
||||
this.reverb = new Tone.Freeverb();
|
||||
|
||||
this.effectSend.chain(this.reverb, this.effectReturn);
|
||||
}
|
||||
|
||||
Tone.extend(ReverbEffect, Tone.Effect);
|
||||
|
||||
ReverbEffect.prototype.set = function (val) {
|
||||
this.value = val;
|
||||
|
||||
this.value = this.clamp(this.value, 0, 100);
|
||||
|
||||
this.reverb.wet.value = this.value / 100;
|
||||
};
|
||||
|
||||
ReverbEffect.prototype.changeBy = function (val) {
|
||||
this.set(this.value + val);
|
||||
};
|
||||
|
||||
ReverbEffect.prototype.clamp = function (input, min, max) {
|
||||
return Math.min(Math.max(input, min), max);
|
||||
};
|
||||
|
||||
module.exports = ReverbEffect;
|
||||
|
59
src/effects/RoboticEffect.js
Normal file
59
src/effects/RoboticEffect.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
/*
|
||||
|
||||
A robot-voice effect
|
||||
|
||||
A feedback comb filter with a short delay time creates a low-pitched buzzing
|
||||
The effect value controls the length of this delay time, changing the pitch
|
||||
|
||||
0 mutes the effect
|
||||
|
||||
Other values changes the pitch of the effect, in units of 10 steps per semitone
|
||||
|
||||
Not clamped
|
||||
|
||||
*/
|
||||
|
||||
var Tone = require('tone');
|
||||
|
||||
function RoboticEffect () {
|
||||
Tone.Effect.call(this);
|
||||
|
||||
this.value = 0;
|
||||
|
||||
var time = this._delayTimeForValue(100);
|
||||
this.feedbackCombFilter = new Tone.FeedbackCombFilter(time, 0.9);
|
||||
|
||||
this.effectSend.chain(this.feedbackCombFilter, this.effectReturn);
|
||||
}
|
||||
|
||||
Tone.extend(RoboticEffect, Tone.Effect);
|
||||
|
||||
RoboticEffect.prototype.set = function (val) {
|
||||
this.value = val;
|
||||
|
||||
// mute the effect if value is 0
|
||||
if (this.value == 0) {
|
||||
this.wet.value = 0;
|
||||
} else {
|
||||
this.wet.value = 1;
|
||||
}
|
||||
|
||||
// set delay time using the value
|
||||
var time = this._delayTimeForValue(this.value);
|
||||
this.feedbackCombFilter.delayTime.rampTo(time, 1/60);
|
||||
};
|
||||
|
||||
RoboticEffect.prototype.changeBy = function (val) {
|
||||
this.set(this.value + val);
|
||||
};
|
||||
|
||||
RoboticEffect.prototype._delayTimeForValue = function (val) {
|
||||
// convert effect setting range, typically 0-100 but can be outside that,
|
||||
// to a musical note, and return the period of the frequency of that note
|
||||
var midiNote = ((val - 100) / 10) + 36;
|
||||
var freq = Tone.Frequency(midiNote, 'midi').eval();
|
||||
return 1 / freq;
|
||||
};
|
||||
|
||||
module.exports = RoboticEffect;
|
||||
|
47
src/effects/WobbleEffect.js
Normal file
47
src/effects/WobbleEffect.js
Normal file
|
@ -0,0 +1,47 @@
|
|||
/*
|
||||
|
||||
A wobble effect
|
||||
|
||||
A low frequency oscillator (LFO) controls a gain node
|
||||
This creates an effect like tremolo
|
||||
|
||||
Clamped 0 to 100
|
||||
|
||||
*/
|
||||
|
||||
var Tone = require('tone');
|
||||
|
||||
function WobbleEffect () {
|
||||
Tone.Effect.call(this);
|
||||
|
||||
this.value = 0;
|
||||
|
||||
this.wobbleLFO = new Tone.LFO(10, 0, 1).start();
|
||||
this.wobbleGain = new Tone.Gain();
|
||||
this.wobbleLFO.connect(this.wobbleGain.gain);
|
||||
|
||||
this.effectSend.chain(this.wobbleGain, this.effectReturn);
|
||||
}
|
||||
|
||||
Tone.extend(WobbleEffect, Tone.Effect);
|
||||
|
||||
WobbleEffect.prototype.set = function (val) {
|
||||
this.value = val;
|
||||
|
||||
this.value = this.clamp(this.value, 0, 100);
|
||||
|
||||
this.wet.value = this.value / 100;
|
||||
|
||||
this.wobbleLFO.frequency.rampTo(this.value / 10, 1/60);
|
||||
};
|
||||
|
||||
WobbleEffect.prototype.changeBy = function (val) {
|
||||
this.set(this.value + val);
|
||||
};
|
||||
|
||||
WobbleEffect.prototype.clamp = function (input, min, max) {
|
||||
return Math.min(Math.max(input, min), max);
|
||||
};
|
||||
|
||||
module.exports = WobbleEffect;
|
||||
|
169
src/index.js
169
src/index.js
|
@ -1,5 +1,14 @@
|
|||
var log = require('./log');
|
||||
var Tone = require('tone');
|
||||
|
||||
var PitchEffect = require('./effects/PitchEffect');
|
||||
var EchoEffect = require('./effects/EchoEffect');
|
||||
var PanEffect = require('./effects/PanEffect');
|
||||
var RoboticEffect = require('./effects/RoboticEffect');
|
||||
var ReverbEffect = require('./effects/ReverbEffect');
|
||||
var FuzzEffect = require('./effects/FuzzEffect');
|
||||
var WobbleEffect = require('./effects/WobbleEffect');
|
||||
|
||||
var SoundPlayer = require('./SoundPlayer');
|
||||
var Soundfont = require('soundfont-player');
|
||||
var ADPCMSoundLoader = require('./ADPCMSoundLoader');
|
||||
|
@ -11,52 +20,31 @@ function AudioEngine (sounds) {
|
|||
this.tone = new Tone();
|
||||
|
||||
// effects setup
|
||||
// each effect has a single parameter controlled by the effects block
|
||||
|
||||
this.delay = new Tone.FeedbackDelay(0.25, 0.5);
|
||||
this.panner = new Tone.Panner();
|
||||
this.reverb = new Tone.Freeverb();
|
||||
this.distortion = new Tone.Distortion(1);
|
||||
this.pitchEffectValue;
|
||||
|
||||
this.robotic = new Tone.Effect();
|
||||
// use the period of a musical note to set the feedback filter delay time
|
||||
var delayTime = 1 / Tone.Frequency('Gb3').eval();
|
||||
var robotFilter = new Tone.FeedbackCombFilter(delayTime, 0.9);
|
||||
this.robotic.effectSend.chain(robotFilter, this.robotic.effectReturn);
|
||||
|
||||
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
|
||||
// using 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);
|
||||
this.pitchEffect = new PitchEffect();
|
||||
this.echoEffect = new EchoEffect();
|
||||
this.panEffect = new PanEffect();
|
||||
this.reverbEffect = new ReverbEffect();
|
||||
this.fuzzEffect = new FuzzEffect();
|
||||
this.wobbleEffect = new WobbleEffect();
|
||||
this.roboticEffect = new RoboticEffect();
|
||||
|
||||
// the effects are chained to an effects node for this clone, then to the master output
|
||||
// so audio is sent from each player or instrument, through the effects in order, then out
|
||||
// note that the pitch effect works differently - it sets the playback rate for each player
|
||||
this.effectsNode = new Tone.Gain();
|
||||
this.effectsNode.chain(
|
||||
this.robotic, this.distortion, this.delay, this.telephone,
|
||||
this.wobble, this.panner, this.reverb, Tone.Master);
|
||||
this.roboticEffect, this.fuzzEffect, this.echoEffect,
|
||||
this.wobbleEffect, this.panEffect, this.reverbEffect, Tone.Master);
|
||||
|
||||
// reset effects to their default parameters
|
||||
this.clearEffects();
|
||||
|
||||
this.effectNames = ['PITCH', 'PAN', 'ECHO', 'REVERB', 'FUZZ', 'TELEPHONE', 'WOBBLE', 'ROBOTIC'];
|
||||
|
||||
|
||||
// load sounds
|
||||
|
||||
this.soundPlayers = [];
|
||||
this.loadSounds(sounds);
|
||||
// Tone.Buffer.on('load', this._soundsLoaded.bind(this));
|
||||
|
||||
// soundfont setup
|
||||
|
||||
|
@ -69,21 +57,12 @@ function AudioEngine (sounds) {
|
|||
'music_box', 'steel_drums', 'marimba', 'lead_1_square', 'fx_4_atmosphere'];
|
||||
|
||||
this.instrumentNum;
|
||||
this.setInstrument(0);
|
||||
this.setInstrument(1);
|
||||
|
||||
// tempo in bpm (beats per minute)
|
||||
// default is 60bpm
|
||||
|
||||
this.currentTempo = 60;
|
||||
|
||||
// theremin setup
|
||||
|
||||
this.theremin = new Tone.Synth();
|
||||
this.portamentoTime = 0.25;
|
||||
this.thereminVibrato = new Tone.Vibrato(4, 0.5);
|
||||
this.theremin.chain(this.thereminVibrato, this.effectsNode);
|
||||
this.thereminTimeout;
|
||||
this.thereminIsPlaying = false;
|
||||
}
|
||||
|
||||
AudioEngine.prototype.loadSounds = function (sounds) {
|
||||
|
@ -151,30 +130,6 @@ AudioEngine.prototype.playNoteForBeats = function (note, beats) {
|
|||
*/
|
||||
};
|
||||
|
||||
AudioEngine.prototype.playThereminForBeats = function (note, beats) {
|
||||
// if the theremin is playing
|
||||
// ramp to new frequency
|
||||
// else
|
||||
// trigger attack
|
||||
// create a timeout for slightly longer than the duration of the block
|
||||
// that releases the theremin - so we can slide continuously between
|
||||
// successive notes without releasing and re-attacking
|
||||
|
||||
var freq = this._midiToFreq(note);
|
||||
|
||||
if (this.thereminIsPlaying) {
|
||||
this.theremin.frequency.rampTo(freq, this.portamentoTime);
|
||||
} else {
|
||||
this.theremin.triggerAttack(freq);
|
||||
this.thereminIsPlaying = true;
|
||||
}
|
||||
clearTimeout(this.thereminTimeout);
|
||||
this.thereminTimeout = setTimeout(function () {
|
||||
this.theremin.triggerRelease();
|
||||
this.thereminIsPlaying = false;
|
||||
}.bind(this), (1000 * beats) + 100);
|
||||
};
|
||||
|
||||
AudioEngine.prototype._midiToFreq = function (midiNote) {
|
||||
var freq = this.tone.intervalToFrequencyRatio(midiNote - 60) * 261.63; // 60 is C4
|
||||
return freq;
|
||||
|
@ -204,28 +159,25 @@ AudioEngine.prototype.stopAllSounds = function () {
|
|||
AudioEngine.prototype.setEffect = function (effect, value) {
|
||||
switch (effect) {
|
||||
case 'PITCH':
|
||||
this._setPitchShift(value);
|
||||
this.pitchEffect.set(value, this.soundPlayers);
|
||||
break;
|
||||
case 'PAN':
|
||||
this.panner.pan.value = value / 100;
|
||||
this.panEffect.set(value);
|
||||
break;
|
||||
case 'ECHO':
|
||||
this.delay.wet.value = (value / 100) / 2; // max 50% wet
|
||||
this.echoEffect.set(value);
|
||||
break;
|
||||
case 'REVERB':
|
||||
this.reverb.wet.value = value / 100;
|
||||
this.reverbEffect.set(value);
|
||||
break;
|
||||
case 'FUZZ' :
|
||||
this.distortion.wet.value = value / 100;
|
||||
break;
|
||||
case 'TELEPHONE' :
|
||||
this.telephone.wet.value = value / 100;
|
||||
this.fuzzEffect.set(value);
|
||||
break;
|
||||
case 'WOBBLE' :
|
||||
this.wobble.wet.value = value / 100;
|
||||
this.wobbleEffect.set(value);
|
||||
break;
|
||||
case 'ROBOTIC' :
|
||||
this.robotic.wet.value = value / 100;
|
||||
this.roboticEffect.set(value);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
@ -233,66 +185,46 @@ AudioEngine.prototype.setEffect = function (effect, value) {
|
|||
AudioEngine.prototype.changeEffect = function (effect, value) {
|
||||
switch (effect) {
|
||||
case 'PITCH':
|
||||
this._setPitchShift(this.pitchEffectValue + Number(value));
|
||||
this.pitchEffect.changeBy(value, this.soundPlayers);
|
||||
break;
|
||||
case 'PAN':
|
||||
this.panner.pan.value += value / 100;
|
||||
this.panner.pan.value = this._clamp(this.panner.pan.value, -1, 1);
|
||||
this.panEffect.changeBy(value);
|
||||
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);
|
||||
this.echoEffect.changeBy(value);
|
||||
break;
|
||||
case 'REVERB':
|
||||
this.reverb.wet.value += value / 100;
|
||||
this.reverb.wet.value = this._clamp(this.reverb.wet.value, 0, 1);
|
||||
this.reverbEffect.changeBy(value);
|
||||
break;
|
||||
case 'FUZZ' :
|
||||
this.distortion.wet.value += value / 100;
|
||||
this.distortion.wet.value = this._clamp(this.distortion.wet.value, 0, 1);
|
||||
break;
|
||||
case 'TELEPHONE' :
|
||||
this.telephone.wet.value += value / 100;
|
||||
this.telephone.wet.value = this._clamp(this.telephone.wet.value, 0, 1);
|
||||
this.fuzzEffect.changeBy(value);
|
||||
break;
|
||||
case 'WOBBLE' :
|
||||
this.wobble.wet.value += value / 100;
|
||||
this.wobble.wet.value = this._clamp(this.wobble.wet.value, 0, 1);
|
||||
this.wobbleEffect.changeBy(value);
|
||||
break;
|
||||
case 'ROBOTIC' :
|
||||
this.robotic.wet.value += value / 100;
|
||||
this.robotic.wet.value = this._clamp(this.robotic.wet.value, 0, 1);
|
||||
this.roboticEffect.changeBy(value);
|
||||
break;
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
AudioEngine.prototype._setPitchShift = function (value) {
|
||||
this.pitchEffectValue = value;
|
||||
AudioEngine.prototype.clearEffects = function () {
|
||||
this.echoEffect.set(0);
|
||||
this.panEffect.set(0);
|
||||
this.reverbEffect.set(0);
|
||||
this.fuzzEffect.set(0);
|
||||
this.roboticEffect.set(0);
|
||||
this.wobbleEffect.set(0);
|
||||
this.pitchEffect.set(0, this.soundPlayers);
|
||||
|
||||
if (!this.soundPlayers) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ratio = this._getPitchRatio();
|
||||
this._setPlaybackRateForAllSoundPlayers(ratio);
|
||||
|
||||
};
|
||||
|
||||
AudioEngine.prototype._setPlaybackRateForAllSoundPlayers = function (rate) {
|
||||
for (var i=0; i<this.soundPlayers.length; i++) {
|
||||
this.soundPlayers[i].setPlaybackRate(rate);
|
||||
}
|
||||
};
|
||||
|
||||
AudioEngine.prototype._getPitchRatio = function () {
|
||||
return this.tone.intervalToFrequencyRatio(this.pitchEffectValue / 10);
|
||||
this.effectsNode.gain.value = 1;
|
||||
};
|
||||
|
||||
AudioEngine.prototype.setInstrument = function (instrumentNum) {
|
||||
this.instrumentNum = instrumentNum;
|
||||
this.instrumentNum = instrumentNum - 1;
|
||||
|
||||
return Soundfont.instrument(Tone.context, this.instrumentNames[instrumentNum]).then(
|
||||
return Soundfont.instrument(Tone.context, this.instrumentNames[this.instrumentNum]).then(
|
||||
function (inst) {
|
||||
this.instrument = inst;
|
||||
this.instrument.connect(this.effectsNode);
|
||||
|
@ -300,19 +232,6 @@ AudioEngine.prototype.setInstrument = function (instrumentNum) {
|
|||
);
|
||||
};
|
||||
|
||||
AudioEngine.prototype.clearEffects = function () {
|
||||
this.delay.wet.value = 0;
|
||||
this.panner.pan.value = 0;
|
||||
this.reverb.wet.value = 0;
|
||||
this.distortion.wet.value = 0;
|
||||
this.robotic.wet.value = 0;
|
||||
this.wobble.wet.value = 0;
|
||||
this.telephone.wet.value = 0;
|
||||
this._setPitchShift(0);
|
||||
|
||||
this.effectsNode.gain.value = 1;
|
||||
};
|
||||
|
||||
AudioEngine.prototype.setVolume = function (value) {
|
||||
var vol = this._clamp(value, 0, 100);
|
||||
vol /= 100;
|
||||
|
|
139
src/vocoder.js
139
src/vocoder.js
|
@ -1,139 +0,0 @@
|
|||
/*
|
||||
|
||||
This is a port of Jamison Dance's port of Chris Wilson's WebAudio vocoder:
|
||||
https://github.com/jergason/Vocoder
|
||||
https://github.com/cwilso/Vocoder
|
||||
|
||||
I have adapted it to use the ToneJs library, making it a Tone Effect that
|
||||
can be added to an audio effects chain.
|
||||
|
||||
*/
|
||||
|
||||
/*
|
||||
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2014 Chris Wilson
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
||||
*/
|
||||
|
||||
var Tone = require('tone');
|
||||
|
||||
function Vocoder () {
|
||||
Tone.Effect.call(this);
|
||||
|
||||
this.modulatorInput = new Tone.Gain();
|
||||
this.carrierInput = new Tone.Gain();
|
||||
this.outputGain = new Tone.Gain();
|
||||
|
||||
this.oscillatorNode;
|
||||
|
||||
this.vocoderBands = this.generateVocoderBands(55, 7040, 8);
|
||||
this.initBandpassFilters();
|
||||
this.createCarrier();
|
||||
|
||||
this.effectSend.connect(this.modulatorInput);
|
||||
this.outputGain.connect(this.effectReturn);
|
||||
}
|
||||
|
||||
Tone.extend(Vocoder, Tone.Effect);
|
||||
|
||||
Vocoder.prototype.setCarrierOscFrequency = function (freq) {
|
||||
this.oscillatorNode.frequency.rampTo(freq, 0.05);
|
||||
};
|
||||
|
||||
// this function algorithmically calculates vocoder bands, distributing evenly
|
||||
// from startFreq to endFreq, splitting evenly (logarhythmically) into a given numBands.
|
||||
Vocoder.prototype.generateVocoderBands = function (startFreq, endFreq, numBands) {
|
||||
// Remember: 1200 cents in octave, 100 cents per semitone
|
||||
|
||||
var totalRangeInCents = 1200 * Math.log( endFreq / startFreq ) / Math.LN2;
|
||||
var centsPerBand = totalRangeInCents / numBands;
|
||||
var scale = Math.pow( 2, centsPerBand / 1200 ); // This is the scaling for successive bands
|
||||
|
||||
var vocoderBands = [];
|
||||
var currentFreq = startFreq;
|
||||
|
||||
for (var i=0; i<numBands; i++) {
|
||||
vocoderBands[i] = new Object();
|
||||
vocoderBands[i].frequency = currentFreq;
|
||||
currentFreq = currentFreq * scale;
|
||||
}
|
||||
|
||||
return vocoderBands;
|
||||
};
|
||||
|
||||
Vocoder.prototype.initBandpassFilters = function () {
|
||||
|
||||
var FILTER_QUALITY = 6; // The Q value for the carrier and modulator filters
|
||||
|
||||
// Set up a high-pass filter to add back in the fricatives, etc.
|
||||
var hpFilter = new Tone.Filter(8000, 'highpass');
|
||||
this.modulatorInput.connect(hpFilter);
|
||||
hpFilter.connect(this.outputGain);
|
||||
|
||||
for (var i=0; i<this.vocoderBands.length; i++) {
|
||||
|
||||
// CREATE THE MODULATOR CHAIN
|
||||
// create the bandpass filter in the modulator chain
|
||||
var modulatorFilter = new Tone.Filter(this.vocoderBands[i].frequency, 'bandpass', -24);
|
||||
modulatorFilter.Q.value = FILTER_QUALITY;
|
||||
this.modulatorInput.connect(modulatorFilter);
|
||||
|
||||
// create a post-filtering gain to bump the levels up.
|
||||
var modulatorFilterPostGain = new Tone.Gain(6);
|
||||
modulatorFilter.connect(modulatorFilterPostGain);
|
||||
|
||||
// add a rectifier with a lowpass filter to turn the bandpass filtered signal
|
||||
// into a smoothed control signal for the carrier filter
|
||||
var rectifier = new Tone.WaveShaper([1,0,1]);
|
||||
modulatorFilterPostGain.connect(rectifier);
|
||||
var rectifierLowPass = new Tone.Filter(50, 'lowpass');
|
||||
rectifier.connect(rectifierLowPass);
|
||||
|
||||
// Create the bandpass filter in the carrier chain
|
||||
var carrierFilter = new Tone.Filter(this.vocoderBands[i].frequency, 'bandpass', -24);
|
||||
carrierFilter.Q.value = FILTER_QUALITY;
|
||||
this.carrierInput.connect(carrierFilter);
|
||||
var carrierFilterPostGain = new Tone.Gain(10);
|
||||
carrierFilter.connect(carrierFilterPostGain);
|
||||
|
||||
// Create the carrier band gain node
|
||||
var bandGain = new Tone.Gain(0);
|
||||
carrierFilterPostGain.connect(bandGain);
|
||||
|
||||
// the modulator band filter output controls the gain of the carrier band
|
||||
rectifierLowPass.connect(bandGain.gain);
|
||||
|
||||
bandGain.connect(this.outputGain);
|
||||
}
|
||||
};
|
||||
|
||||
Vocoder.prototype.createCarrier = function () {
|
||||
this.oscillatorNode = new Tone.Oscillator('C3', 'sawtooth').start();
|
||||
this.oscillatorNode.connect(this.carrierInput);
|
||||
|
||||
var noiseNode = new Tone.Noise('white').start();
|
||||
noiseNode.volume.value = -12;
|
||||
noiseNode.connect(this.carrierInput);
|
||||
};
|
||||
|
||||
module.exports = Vocoder;
|
Loading…
Reference in a new issue