Skip to content

Instantly share code, notes, and snippets.

@imolorhe
Created April 21, 2019 23:21
Show Gist options
  • Save imolorhe/b31f95e1548ad7d1b233de26267fe21c to your computer and use it in GitHub Desktop.
Save imolorhe/b31f95e1548ad7d1b233de26267fe21c to your computer and use it in GitHub Desktop.
// Copyright 2012, Google Inc.
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following disclaimer
// in the documentation and/or other materials provided with the
// distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived from
// this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
function createFadeBuffer(context, activeTime, fadeTime) {
var length1 = activeTime * context.sampleRate;
var length2 = (activeTime - 2*fadeTime) * context.sampleRate;
var length = length1 + length2;
var buffer = context.createBuffer(1, length, context.sampleRate);
var p = buffer.getChannelData(0);
console.log("createFadeBuffer() length = " + length);
var fadeLength = fadeTime * context.sampleRate;
var fadeIndex1 = fadeLength;
var fadeIndex2 = length1 - fadeLength;
// 1st part of cycle
for (var i = 0; i < length1; ++i) {
var value;
if (i < fadeIndex1) {
value = Math.sqrt(i / fadeLength);
} else if (i >= fadeIndex2) {
value = Math.sqrt(1 - (i - fadeIndex2) / fadeLength);
} else {
value = 1;
}
p[i] = value;
}
// 2nd part
for (var i = length1; i < length; ++i) {
p[i] = 0;
}
return buffer;
}
function createDelayTimeBuffer(context, activeTime, fadeTime, shiftUp) {
var length1 = activeTime * context.sampleRate;
var length2 = (activeTime - 2*fadeTime) * context.sampleRate;
var length = length1 + length2;
var buffer = context.createBuffer(1, length, context.sampleRate);
var p = buffer.getChannelData(0);
console.log("createDelayTimeBuffer() length = " + length);
// 1st part of cycle
for (var i = 0; i < length1; ++i) {
if (shiftUp)
// This line does shift-up transpose
p[i] = (length1-i)/length;
else
// This line does shift-down transpose
p[i] = i / length1;
}
// 2nd part
for (var i = length1; i < length; ++i) {
p[i] = 0;
}
return buffer;
}
var delayTime = 0.100;
var fadeTime = 0.050;
var bufferTime = 0.100;
function Jungle(context) {
this.context = context;
// Create nodes for the input and output of this "module".
var input = context.createGain();
var output = context.createGain();
this.input = input;
this.output = output;
// Delay modulation.
var mod1 = context.createBufferSource();
var mod2 = context.createBufferSource();
var mod3 = context.createBufferSource();
var mod4 = context.createBufferSource();
this.shiftDownBuffer = createDelayTimeBuffer(context, bufferTime, fadeTime, false);
this.shiftUpBuffer = createDelayTimeBuffer(context, bufferTime, fadeTime, true);
mod1.buffer = this.shiftDownBuffer;
mod2.buffer = this.shiftDownBuffer;
mod3.buffer = this.shiftUpBuffer;
mod4.buffer = this.shiftUpBuffer;
mod1.loop = true;
mod2.loop = true;
mod3.loop = true;
mod4.loop = true;
// for switching between oct-up and oct-down
var mod1Gain = context.createGain();
var mod2Gain = context.createGain();
var mod3Gain = context.createGain();
mod3Gain.gain.value = 0;
var mod4Gain = context.createGain();
mod4Gain.gain.value = 0;
mod1.connect(mod1Gain);
mod2.connect(mod2Gain);
mod3.connect(mod3Gain);
mod4.connect(mod4Gain);
// Delay amount for changing pitch.
var modGain1 = context.createGain();
var modGain2 = context.createGain();
var delay1 = context.createDelay();
var delay2 = context.createDelay();
mod1Gain.connect(modGain1);
mod2Gain.connect(modGain2);
mod3Gain.connect(modGain1);
mod4Gain.connect(modGain2);
modGain1.connect(delay1.delayTime);
modGain2.connect(delay2.delayTime);
// Crossfading.
var fade1 = context.createBufferSource();
var fade2 = context.createBufferSource();
var fadeBuffer = createFadeBuffer(context, bufferTime, fadeTime);
fade1.buffer = fadeBuffer
fade2.buffer = fadeBuffer;
fade1.loop = true;
fade2.loop = true;
var mix1 = context.createGain();
var mix2 = context.createGain();
mix1.gain.value = 0;
mix2.gain.value = 0;
fade1.connect(mix1.gain);
fade2.connect(mix2.gain);
// Connect processing graph.
input.connect(delay1);
input.connect(delay2);
delay1.connect(mix1);
delay2.connect(mix2);
mix1.connect(output);
mix2.connect(output);
// Start
var t = context.currentTime + 0.050;
var t2 = t + bufferTime - fadeTime;
mod1.start(t);
mod2.start(t2);
mod3.start(t);
mod4.start(t2);
fade1.start(t);
fade2.start(t2);
this.mod1 = mod1;
this.mod2 = mod2;
this.mod1Gain = mod1Gain;
this.mod2Gain = mod2Gain;
this.mod3Gain = mod3Gain;
this.mod4Gain = mod4Gain;
this.modGain1 = modGain1;
this.modGain2 = modGain2;
this.fade1 = fade1;
this.fade2 = fade2;
this.mix1 = mix1;
this.mix2 = mix2;
this.delay1 = delay1;
this.delay2 = delay2;
this.setDelay(delayTime);
}
Jungle.prototype.setDelay = function(delayTime) {
this.modGain1.gain.setTargetAtTime(0.5*delayTime, 0, 0.010);
this.modGain2.gain.setTargetAtTime(0.5*delayTime, 0, 0.010);
}
var previousPitch = -1;
Jungle.prototype.setPitchOffset = function(mult) {
if (mult>0) { // pitch up
this.mod1Gain.gain.value = 0;
this.mod2Gain.gain.value = 0;
this.mod3Gain.gain.value = 1;
this.mod4Gain.gain.value = 1;
} else { // pitch down
this.mod1Gain.gain.value = 1;
this.mod2Gain.gain.value = 1;
this.mod3Gain.gain.value = 0;
this.mod4Gain.gain.value = 0;
}
this.setDelay(delayTime*Math.abs(mult));
previousPitch = mult;
}
Jungle.prototype.mapPitchFromSemitone = function(e) {
if (e < 0)
return e / 12;
switch (e) {
case 0:
return 0;
case 1:
return .15;
case 2:
return .2396498469723199;
case 3:
return .3392416223392665;
case 4:
return .5240613184290452;
case 5:
return .6938443526464626;
case 6:
return .8531707712586548;
case 7:
return 1.0062309336345026;
case 8:
return 1.1572897600301169;
case 9:
return 1.3572897600301168;
case 10:
return 1.5356230933634503;
case 11:
return 1.7056230933634502;
case 12:
return 2;
default:
return 0
}
}
,
Jungle.prototype.setPitchTranspose = function(e, a) {
a = parseInt(a),
e = parseFloat(e);
var t = this.mapPitchFromSemitone(a) + 1 * e / 12;
this.setPitchOffset(t)
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment