// ==UserScript==
// @name WhatsApp online notifier
// @namespace https://bennyjacobs.nl/userscripts/WhatsApp-Online-Notifier
// @description Notifies you when a user comes online. This script will only work if have a tab open and navigated to https://web.whatsapp.com/ and have selected a person. The next time this person comes online you should hear a bleeb. *Bleeb*!
// @include https://web.whatsapp.com/
// @version 0.0.2
// @grant none
// ==/UserScript==
//
// The MIT License (MIT)
//
// Copyright (c) 2015 Loov
//
// 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.
//
(function(jsfx){
'use strict';
var chr = String.fromCharCode;
var TAU = +Math.PI*2;
var bitsPerSample = 16|0;
var numChannels = 1|0;
var sin = Math.sin;
var pow = Math.pow;
var abs = Math.abs;
var EPSILON = 0.000001;
jsfx.SampleRate = 0|0;
jsfx.Sec = 0|0;
jsfx.SetSampleRate = function(sampleRate){
jsfx.SampleRate = sampleRate|0;
jsfx.Sec = sampleRate|0;
};
jsfx.SetSampleRate(getDefaultSampleRate());
// MAIN API
// Creates a new Audio object based on the params
// params can be a params generating function or the actual parameters
jsfx.Sound = function(params){
var processor = new Processor(params, jsfx.DefaultModules);
var block = createFloatArray(processor.getSamplesLeft());
processor.generate(block);
return CreateAudio(block);
};
// Same as Sounds, but avoids locking the browser for too long
// in case you have a large amount of sounds to generate
jsfx.Sounds = function(library, ondone, onprogress){
var audio = {};
var player = {};
player._audio = audio;
var toLoad = [];
// create playing functions
map_object(library, function(_, name){
player[name] = function(){
if(typeof audio[name] !== "undefined"){
audio[name].currentTime = 0.0;
audio[name].play();
}
};
toLoad.push(name);
});
var loaded = 0, total = toLoad.length;
function next(){
if(toLoad.length == 0){
ondone && ondone(sounds);
return;
}
var name = toLoad.shift();
audio[name] = jsfx.Sound(library[name]);
loaded++;
onprogress && onprogress(name, loaded, total);
window.setTimeout(next, 30);
}
next();
return player;
}
// SoundsImmediate takes a named set of params, and generates multiple
// sound objects at once.
jsfx.SoundsImmediate = function(library){
var audio = {};
var player = {};
player._audio = audio;
map_object(library, function(params, name){
audio[name] = jsfx.Sound(params);
player[name] = function(){
if(typeof audio[name] !== "undefined"){
audio[name].currentTime = 0.0;
audio[name].play();
}
};
})
return player;
};
if(typeof AudioContext !== "undefined"){
// Node creates a new AudioContext ScriptProcessor that outputs the
// sound. It will automatically disconnect, unless otherwise specified.
jsfx.Node = function(audioContext, params, modules, bufferSize, stayConnected){
var node = audioContext.createScriptProcessor(bufferSize, 0, 1);
var gen = new Processor(params, modules || jsfx.DefaultModules);
node.onaudioprocess = function(ev){
var block = ev.outputBuffer.getChannelData(0);
gen.generate(block);
if(!stayConnected && gen.finished){
// we need to do an async disconnect, otherwise Chrome may
// glitch
setTimeout(function(){ node.disconnect(); }, 30);
}
}
return node;
}
// Live creates an managed AudioContext for playing.
// This is useful, when you want to use procedurally generated sounds.
jsfx.Live = function(library, modules, BufferSize){
//TODO: add limit for number of notes played at the same time
BufferSize = BufferSize || 2048;
var player = {};
var context = new AudioContext();
var volume = context.createGain();
volume.connect(context.destination);
player._context = context;
player._volume = volume;
map_object(library, function(params, name){
player[name] = function(){
var node = jsfx.Node(context, params, modules, BufferSize);
node.connect(volume);
};
});
player._close = function(){
context.close();
};
player._play = function(params){
var node = jsfx.Node(context, params, modules, BufferSize);
node.connect(volume);
};
return player;
}
} else {
jsfx.Live = jsfx.Sounds;
}
// SOUND GENERATION
jsfx.Module = {};
// generators
jsfx.G = {};
var stage = jsfx.stage = {
PhaseSpeed : 0,
PhaseSpeedMod : 10,
Generator : 20,
SampleMod : 30,
Volume : 40
};
function byStage(a,b){ return a.stage - b.stage; }
jsfx.InitDefaultParams = InitDefaultParams;
function InitDefaultParams(params, modules){
// setup modules
for(var i = 0; i < modules.length; i += 1){
var M = modules[i];
var P = params[M.name] || {};
// add missing parameters
map_object(M.params, function(def, name){
if(typeof P[name] === 'undefined'){
P[name] = def.D;
}
});
params[M.name] = P;
}
}
// Generates a stateful sound effect processor
// params can be a function that creates a parameter set
jsfx.Processor = Processor;
function Processor(params, modules){
params = params || {};
modules = modules || jsfx.DefaultModules;
if(typeof params === 'function'){
params = params();
} else {
params = JSON.parse(JSON.stringify(params))
}
this.finished = false;
this.state = {
SampleRate: params.SampleRate || jsfx.SampleRate
};
// sort modules
modules = modules.slice();
modules.sort(byStage)
this.modules = modules;
// init missing params
InitDefaultParams(params, modules);
// setup modules
for(var i = 0; i < this.modules.length; i += 1){
var M = this.modules[i];
this.modules[i].setup(this.state, params[M.name]);
}
}
Processor.prototype = {
//TODO: see whether this can be converted to a module
generate: function(block){
for(var i = 0|0; i < block.length; i += 1){
block[i] = 0;
}
if(this.finished){ return; }
var $ = this.state,
N = block.length|0;
for(var i = 0; i < this.modules.length; i += 1){
var M = this.modules[i];
var n = M.process($, block.subarray(0,N))|0;
N = Math.min(N, n);
}
if(N < block.length){
this.finished = true;
}
for(var i = N; i < block.length; i++){
block[i] = 0;
}
},
getSamplesLeft: function(){
var samples = 0;
for(var i = 0; i < this.state.envelopes.length; i += 1){
samples += this.state.envelopes[i].N;
}
if(samples === 0){
samples = 3*this.state.SampleRate;
}
return samples;
}
};
// Frequency
jsfx.Module.Frequency = {
name: 'Frequency',
params: {
Start: { L:30, H:1800, D:440 },
Min: { L:30, H:1800, D:30 },
Max: { L:30, H:1800, D:1800 },
Slide: { L:-1, H:1, D:0 },
DeltaSlide: { L:-1, H:1, D:0 },
RepeatSpeed: { L:0, H: 3.0, D: 0 },
ChangeAmount: { L:-12, H:12, D:0 },
ChangeSpeed : { L: 0, H:1, D:0 }
},
stage: stage.PhaseSpeed,
setup: function($, P){
var SR = $.SampleRate;
$.phaseParams = P;
$.phaseSpeed = P.Start * TAU / SR;
$.phaseSpeedMax = P.Max * TAU / SR;
$.phaseSpeedMin = P.Min * TAU / SR;
$.phaseSpeedMin = Math.min($.phaseSpeedMin, $.phaseSpeed);
$.phaseSpeedMax = Math.max($.phaseSpeedMax, $.phaseSpeed);
$.phaseSlide = 1.0 + pow(P.Slide, 3.0) * 64.0 / SR;
$.phaseDeltaSlide = pow(P.DeltaSlide, 3.0) / (SR * 1000);
$.repeatTime = 0;
$.repeatLimit = Infinity;
if(P.RepeatSpeed > 0){
$.repeatLimit = P.RepeatSpeed * SR;
}
$.arpeggiatorTime = 0;
$.arpeggiatorLimit = P.ChangeSpeed * SR;
if(P.ChangeAmount == 0){
$.arpeggiatorLimit = Infinity;
}
$.arpeggiatorMod = 1 + P.ChangeAmount / 12.0;
},
process: function($, block){
var speed = +$.phaseSpeed,
min = +$.phaseSpeedMin,
max = +$.phaseSpeedMax,
slide = +$.phaseSlide,
deltaSlide = +$.phaseDeltaSlide;
var repeatTime = $.repeatTime,
repeatLimit = $.repeatLimit;
var arpTime = $.arpeggiatorTime,
arpLimit = $.arpeggiatorLimit,
arpMod = $.arpeggiatorMod;
for(var i = 0; i < block.length; i++){
slide += deltaSlide;
speed *= slide;
speed = speed < min ? min : speed > max ? max : speed;
if(repeatTime > repeatLimit){
this.setup($, $.phaseParams);
return i + this.process($, block.subarray(i)) - 1;
}
repeatTime++;
if(arpTime > arpLimit){
speed *= arpMod;
arpTime = 0;
arpLimit = Infinity;
}
arpTime++;
block[i] += speed;
}
$.repeatTime = repeatTime;
$.arpeggiatorTime = arpTime;
$.arpeggiatorLimit = arpLimit;
$.phaseSpeed = speed;
$.phaseSlide = slide;
return block.length;
}
};
// Vibrato
jsfx.Module.Vibrato = {
name: 'Vibrato',
params: {
Depth: {L: 0, H:1, D:0},
DepthSlide: {L:-1, H:1, D:0},
Frequency: {L: 0.01, H:48, D:0},
FrequencySlide: {L: -1.00, H: 1, D:0}
},
stage: stage.PhaseSpeedMod,
setup: function($, P){
var SR = $.SampleRate;
$.vibratoPhase = 0;
$.vibratoDepth = P.Depth;
$.vibratoPhaseSpeed = P.Frequency * TAU / SR;
$.vibratoPhaseSpeedSlide = 1.0 + pow(P.FrequencySlide, 3.0) * 3.0 / SR;
$.vibratoDepthSlide = P.DepthSlide / SR;
},
process: function($, block){
var phase = +$.vibratoPhase,
depth = +$.vibratoDepth,
speed = +$.vibratoPhaseSpeed,
slide = +$.vibratoPhaseSpeedSlide,
depthSlide = +$.vibratoDepthSlide;
if((depth == 0) && (depthSlide <= 0)){
return block.length;
}
for(var i = 0; i < block.length; i++){
phase += speed;
if(phase > TAU){phase -= TAU};
block[i] += block[i] * sin(phase) * depth;
speed *= slide;
depth += depthSlide;
depth = clamp1(depth);
}
$.vibratoPhase = phase;
$.vibratoDepth = depth;
$.vibratoPhaseSpeed = speed;
return block.length;
}
};
// Generator
jsfx.Module.Generator = {
name: 'Generator',
params: {
// C = choose
Func: {C: jsfx.G, D:'square'},
A: {L: 0, H: 1, D: 0},
B: {L: 0, H: 1, D: 0},
ASlide: {L: -1, H: 1, D: 0},
BSlide: {L: -1, H: 1, D: 0}
},
stage: stage.Generator,
setup: function($, P){
$.generatorPhase = 0;
if(typeof P.Func === 'string'){
$.generator = jsfx.G[P.Func];
} else {
$.generator = P.Func;
}
if(typeof $.generator === 'object'){
$.generator = $.generator.create();
}
assert(typeof $.generator === 'function', 'generator must be a function')
$.generatorA = P.A;
$.generatorASlide = P.ASlide;
$.generatorB = P.B;
$.generatorBSlide = P.BSlide;
},
process: function($, block){
return $.generator($, block);
}
};
// Karplus Strong algorithm for string sound
var GuitarBufferSize = 1 << 16;
jsfx.Module.Guitar = {
name: 'Guitar',
params: {
A: {L:0.0, H:1.0, D: 1},
B: {L:0.0, H:1.0, D: 1},
C: {L:0.0, H:1.0, D: 1},
},
stage: stage.Generator,
setup: function($, P){
$.guitarA = P.A;
$.guitarB = P.B;
$.guitarC = P.C;
$.guitarBuffer = createFloatArray(GuitarBufferSize);
$.guitarHead = 0;
var B = $.guitarBuffer;
for(var i = 0; i < B.length; i++){
B[i] = Math.random()*2 - 1;
}
},
process: function($, block){
var BS = GuitarBufferSize,
BM = BS - 1;
var A = +$.guitarA, B = +$.guitarB, C = +$.guitarC;
var T = A + B + C;
var h = $.guitarHead;
var buffer = $.guitarBuffer;
for(var i = 0; i < block.length; i++){
// buffer size
var n = (TAU / block[i])|0;
n = n > BS ? BS : n;
// tail
var t = ((h - n) + BS) & BM;
buffer[h] =
(buffer[(t-0+BS)&BM]*A +
buffer[(t-1+BS)&BM]*B +
buffer[(t-2+BS)&BM]*C) / T;
block[i] = buffer[h];
h = (h + 1) & BM;
}
$.guitarHead = h;
return block.length;
}
}
// Low/High-Pass Filter
jsfx.Module.Filter = {
name: 'Filter',
params: {
LP: {L: 0, H:1, D:1},
LPSlide: {L:-1, H:1, D:0},
LPResonance: {L: 0, H:1, D:0},
HP: {L: 0, H:1, D:0},
HPSlide: {L:-1, H:1, D:0}
},
stage: stage.SampleMod + 0,
setup: function($, P){
$.FilterEnabled = (P.HP > EPSILON) || (P.LP < 1 - EPSILON);
$.LPEnabled = P.LP < 1 - EPSILON;
$.LP = pow(P.LP, 3.0) / 10;
$.LPSlide = 1.0 + P.LPSlide * 100 / $.SampleRate;
$.LPPos = 0;
$.LPPosSlide = 0;
$.LPDamping = 5.0 / (1.0 + pow(P.LPResonance, 2) * 20) * (0.01 + P.LP);
$.LPDamping = 1.0 - Math.min($.LPDamping, 0.8);
$.HP = pow(P.HP, 2.0) / 10;
$.HPPos = 0;
$.HPSlide = 1.0 + P.HPSlide * 100 / $.SampleRate;
},
enabled: function($){
return $.FilterEnabled;
},
process: function($, block){
if(!this.enabled($)){ return block.length; }
var lp = +$.LP;
var lpPos = +$.LPPos;
var lpPosSlide = +$.LPPosSlide;
var lpSlide = +$.LPSlide;
var lpDamping = +$.LPDamping;
var lpEnabled = +$.LPEnabled;
var hp = +$.HP;
var hpPos = +$.HPPos;
var hpSlide = +$.HPSlide;
for(var i = 0; i < block.length; i++){
if((hp > EPSILON) || (hp < -EPSILON)){
hp *= hpSlide;
hp = hp < EPSILON ? EPSILON: hp > 0.1 ? 0.1 : hp;
}
var lpPos_ = lpPos;
lp *= lpSlide;
lp = lp < 0 ? lp = 0 : lp > 0.1 ? 0.1 : lp;
var sample = block[i];
if(lpEnabled){
lpPosSlide += (sample - lpPos) * lp;
lpPosSlide *= lpDamping;
} else {
lpPos = sample;
lpPosSlide = 0;
}
lpPos += lpPosSlide;
hpPos += lpPos - lpPos_;
hpPos *= 1.0 - hp;
block[i] = hpPos;
}
$.LPPos = lpPos;
$.LPPosSlide = lpPosSlide;
$.LP = lp;
$.HP = hp;
$.HPPos = hpPos;
return block.length;
}
};
// Phaser Effect
var PhaserBufferSize = 1 << 10;
jsfx.Module.Phaser = {
name: 'Phaser',
params: {
Offset: {L:-1, H:1, D:0},
Sweep: {L:-1, H:1, D:0}
},
stage: stage.SampleMod + 1,
setup: function($, P){
$.phaserBuffer = createFloatArray(PhaserBufferSize);
$.phaserPos = 0;
$.phaserOffset = pow(P.Offset, 2.0) * (PhaserBufferSize - 4);
$.phaserOffsetSlide = pow(P.Sweep, 3.0) * 4000 / $.SampleRate;
},
enabled: function($){
return (abs($.phaserOffsetSlide) > EPSILON) ||
(abs($.phaserOffset) > EPSILON);
},
process: function($, block){
if(!this.enabled($)){ return block.length; }
var BS = PhaserBufferSize,
BM = BS - 1;
var buffer = $.phaserBuffer,
pos = $.phaserPos|0,
offset = +$.phaserOffset,
offsetSlide = +$.phaserOffsetSlide;
for(var i = 0; i < block.length; i++){
offset += offsetSlide;
//TODO: check whether this is correct
if(offset < 0){
offset = -offset;
offsetSlide = -offsetSlide;
}
if(offset > BM){
offset = BM;
offsetSlide = 0;
}
buffer[pos] = block[i];
var p = (pos - (offset|0) + BS) & BM;
block[i] += buffer[p];
pos = ((pos + 1) & BM)|0;
}
$.phaserPos = pos;
$.phaserOffset = offset;
return block.length;
}
};
// Volume dynamic control with Attack-Sustain-Decay
// ATTACK | 0 - Volume + Punch
// SUSTAIN | Volume + Punch - Volume
// DECAY | Volume - 0
jsfx.Module.Volume = {
name: 'Volume',
params: {
Master: { L: 0, H: 1, D: 0.5 },
Attack: { L: 0.001, H: 1, D: 0.01 },
Sustain: { L: 0, H: 2, D: 0.3 },
Punch: { L: 0, H: 3, D: 1.0 },
Decay: { L: 0.001, H: 2, D: 1.0 }
},
stage: stage.Volume,
setup: function($, P){
var SR = $.SampleRate;
var V = P.Master;
var VP = V * (1 + P.Punch);
$.envelopes = [
// S = start volume, E = end volume, N = duration in samples
{S: 0, E: V, N: (P.Attack * SR)|0 }, // Attack
{S:VP, E: V, N: (P.Sustain * SR)|0 }, // Sustain
{S: V, E: 0, N: (P.Decay * SR)|0 } // Decay
];
// G = volume gradient
for(var i = 0; i < $.envelopes.length; i += 1){
var e = $.envelopes[i];
e.G = (e.E - e.S) / e.N;
}
},
process: function($, block){
var i = 0;
while(($.envelopes.length > 0) && (i < block.length)){
var E = $.envelopes[0];
var vol = E.S,
grad = E.G;
var N = Math.min(block.length - i, E.N)|0;
var end = (i+N)|0;
for(; i < end; i += 1){
block[i] *= vol;
vol += grad;
vol = clamp(vol, 0, 10);
}
E.S = vol;
E.N -= N;
if(E.N <= 0){
$.envelopes.shift();
}
}
return i;
}
};
// PRESETS
jsfx.DefaultModules = [
jsfx.Module.Frequency,
jsfx.Module.Vibrato,
jsfx.Module.Generator,
jsfx.Module.Filter,
jsfx.Module.Phaser,
jsfx.Module.Volume
];
jsfx.DefaultModules.sort(byStage);
jsfx.EmptyParams = EmptyParams;
function EmptyParams(){
return map_object(jsfx.Module, function(){ return {} });
}
jsfx._RemoveEmptyParams = RemoveEmptyParams;
function RemoveEmptyParams(params){
for(var name in params){
if(Object_keys(params[name]).length == 0){
delete params[name];
}
}
};
jsfx.Preset = {
Reset: function(){
return EmptyParams();
},
Coin: function(){
var p = EmptyParams();
p.Frequency.Start = runif(880, 660);
p.Volume.Sustain = runif(0.1);
p.Volume.Decay = runif(0.4, 0.1);
p.Volume.Punch = runif(0.3, 0.3);
if(runif() < 0.5){
p.Frequency.ChangeSpeed = runif(0.15, 0.1);
p.Frequency.ChangeAmount = runif(8, 4);
}
RemoveEmptyParams(p);
return p;
},
Laser: function(){
var p = EmptyParams();
p.Generator.Func = rchoose(['square', 'saw', 'sine']);
if(runif() < 0.33){
p.Frequency.Start = runif(880, 440);
p.Frequency.Min = runif(0.1);
p.Frequency.Slide = runif(0.3, -0.8);
} else {
p.Frequency.Start = runif(1200, 440);
p.Frequency.Min = p.Frequency.Start - runif(880, 440);
if(p.Frequency.Min < 110){ p.Frequency.Min = 110; }
p.Frequency.Slide = runif(0.3, -1);
}
if(runif() < 0.5){
p.Generator.A = runif(0.5);
p.Generator.ASlide = runif(0.2);
} else {
p.Generator.A = runif(0.5, 0.4);
p.Generator.ASlide = runif(0.7);
}
p.Volume.Sustain = runif(0.2, 0.1);
p.Volume.Decay = runif(0.4);
if(runif() < 0.5){
p.Volume.Punch = runif(0.3);
}
if(runif() < 0.33){
p.Phaser.Offset = runif(0.2);
p.Phaser.Sweep = runif(0.2);
}
if(runif() < 0.5){
p.Filter.HP = runif(0.3);
}
RemoveEmptyParams(p);
return p;
},
Explosion: function(){
var p = EmptyParams();
p.Generator.Func = 'noise';
if(runif() < 0.5){
p.Frequency.Start = runif(440, 40);
p.Frequency.Slide = runif(0.4, -0.1);
} else {
p.Frequency.Start = runif(1600, 220);
p.Frequency.Slide = runif(-0.2, -0.2);
}
if(runif() < 0.2){ p.Frequency.Slide = 0; }
if(runif() < 0.3){ p.Frequency.RepeatSpeed = runif(0.5, 0.3); }
p.Volume.Sustain = runif(0.3, 0.1);
p.Volume.Decay = runif(0.5);
p.Volume.Punch = runif(0.6, 0.2);
if(runif() < 0.5){
p.Phaser.Offset = runif(0.9, -0.3);
p.Phaser.Sweep = runif(-0.3);
}
if(runif() < 0.33){
p.Frequency.ChangeSpeed = runif(0.3, 0.6);
p.Frequency.ChangeAmount = runif(24, -12);
}
RemoveEmptyParams(p);
return p;
},
Powerup: function(){
var p = EmptyParams();
if(runif() < 0.5){
p.Generator.Func = 'saw';
} else {
p.Generator.A = runif(0.6);
}
p.Frequency.Start = runif(220, 440);
if(runif() < 0.5){
p.Frequency.Slide = runif(0.5, 0.2);
p.Frequency.RepeatSpeed = runif(0.4, 0.4);
} else {
p.Frequency.Slide = runif(0.2, 0.05);
if(runif() < 0.5){
p.Vibrato.Depth = runif(0.6, 0.1);
p.Vibrato.Frequency = runif(30, 10);
}
}
p.Volume.Sustain = runif(0.4);
p.Volume.Decay = runif(0.4, 0.1);
RemoveEmptyParams(p);
return p;
},
Hit: function(){
var p = EmptyParams();
p.Generator.Func = rchoose(['square', 'saw', 'noise']);
p.Generator.A = runif(0.6);
p.Generator.ASlide = runif(1, -0.5);
p.Frequency.Start = runif(880, 220);
p.Frequency.Slide = -runif(0.4, 0.3);
p.Volume.Sustain = runif(0.1);
p.Volume.Decay = runif(0.2, 0.1);
if(runif() < 0.5){
p.Filter.HP = runif(0.3);
}
RemoveEmptyParams(p);
return p;
},
Jump: function(){
var p = EmptyParams();
p.Generator.Func = 'square';
p.Generator.A = runif(0.6);
p.Frequency.Start = runif(330, 330);
p.Frequency.Slide = runif(0.4, 0.2);
p.Volume.Sustain = runif(0.3, 0.1);
p.Volume.Decay = runif(0.2, 0.1);
if(runif() < 0.5){
p.Filter.HP = runif(0.3);
}
if(runif() < 0.3){
p.Filter.LP = runif(-0.6, 1);
}
RemoveEmptyParams(p);
return p;
},
Select: function(){
var p = EmptyParams();
p.Generator.Func = rchoose(['square', 'saw']);
p.Generator.A = runif(0.6);
p.Frequency.Start = runif(660, 220);
p.Volume.Sustain = runif(0.1, 0.1);
p.Volume.Decay = runif(0.2);
p.Filter.HP = 0.2;
RemoveEmptyParams(p);
return p;
},
Lucky: function(){
var p = EmptyParams();
map_object(p, function(out, moduleName){
var defs = jsfx.Module[moduleName].params;
map_object(defs, function(def, name){
if(def.C){
var values = Object_keys(def.C);
out[name] = values[(values.length * Math.random()) | 0];
} else {
out[name] = Math.random() * (def.H - def.L) + def.L;
}
});
});
p.Volume.Master = 0.4;
p.Filter = {}; // disable filter, as it usually will clip everything
RemoveEmptyParams(p);
return p;
}
};
// GENERATORS
// uniform noise
jsfx.G.unoise = newGenerator("sample = Math.random();");
// sine wave
jsfx.G.sine = newGenerator("sample = Math.sin(phase);");
// saw wave
jsfx.G.saw = newGenerator("sample = 2*(phase/TAU - ((phase/TAU + 0.5)|0));");
// triangle wave
jsfx.G.triangle = newGenerator("sample = Math.abs(4 * ((phase/TAU - 0.25)%1) - 2) - 1;");
// square wave
jsfx.G.square = newGenerator("var s = Math.sin(phase); sample = s > A ? 1.0 : s < A ? -1.0 : A;");
// simple synth
jsfx.G.synth = newGenerator("sample = Math.sin(phase) + .5*Math.sin(phase/2) + .3*Math.sin(phase/4);");
// STATEFUL
var __noiseLast = 0;
jsfx.G.noise = newGenerator("if(phase % TAU < 4){__noiseLast = Math.random() * 2 - 1;} sample = __noiseLast;");
// Karplus-Strong string
jsfx.G.string = {
create: function(){
var BS = 1 << 16;
var BM = BS-1;
var buffer = createFloatArray(BS);
for(var i = 0; i < buffer.length; i++){
buffer[i] = Math.random()*2-1;
}
var head = 0;
return function($, block){
var TAU = Math.PI * 2;
var A = +$.generatorA, ASlide = +$.generatorASlide,
B = +$.generatorB, BSlide = +$.generatorBSlide;
var buf = buffer;
for(var i = 0; i < block.length; i++){
var phaseSpeed = block[i];
var n = (TAU/phaseSpeed)|0;
A += ASlide; B += BSlide;
A = A < 0 ? 0 : A > 1 ? 1 : A;
B = B < 0 ? 0 : B > 1 ? 1 : B;
var t = ((head - n) + BS) & BM;
var sample = (
buf[(t-0+BS)&BM]*1 +
buf[(t-1+BS)&BM]*A +
buf[(t-2+BS)&BM]*B) / (1+A+B);
buf[head] = sample;
block[i] = buf[head];
head = (head + 1) & BM;
}
$.generatorA = A;
$.generatorB = B;
return block.length;
}
}
};
// Generates samples using given frequency and generator
function newGenerator(line){
return new Function("$", "block", "" +
"var TAU = Math.PI * 2;\n" +
"var sample;\n" +
"var phase = +$.generatorPhase,\n"+
" A = +$.generatorA, ASlide = +$.generatorASlide,\n"+
" B = +$.generatorB, BSlide = +$.generatorBSlide;\n"+
"\n"+
"for(var i = 0; i < block.length; i++){\n"+
" var phaseSpeed = block[i];\n"+
" phase += phaseSpeed;\n"+
" if(phase > TAU){ phase -= TAU };\n"+
" A += ASlide; B += BSlide;\n"+
" A = A < 0 ? 0 : A > 1 ? 1 : A;\n"+
" B = B < 0 ? 0 : B > 1 ? 1 : B;\n"+
line +
" block[i] = sample;\n"+
"}\n"+
"\n"+
"$.generatorPhase = phase;\n"+
"$.generatorA = A;\n"+
"$.generatorB = B;\n"+
"return block.length;\n" +
"");
}
// WAVE SUPPORT
// Creates an Audio element from audio data [-1.0 .. 1.0]
jsfx.CreateAudio = CreateAudio;
function CreateAudio(data){
if(typeof Float32Array !== "undefined"){
assert(data instanceof Float32Array, 'data must be an Float32Array');
}
var blockAlign = numChannels * bitsPerSample >> 3;
var byteRate = jsfx.SampleRate * blockAlign;
var output = createByteArray(8 + 36 + data.length * 2);
var p = 0;
// emits string to output
function S(value){
for(var i = 0; i < value.length; i += 1){
output[p] = value.charCodeAt(i); p++;
}
}
// emits integer value to output
function V(value, nBytes){
if(nBytes <= 0){ return; }
output[p] = value & 0xFF; p++;
V(value >> 8, nBytes - 1);
}
S('RIFF'); V(36 + data.length * 2, 4);
S('WAVEfmt '); V(16, 4); V(1, 2);
V(numChannels, 2); V(jsfx.SampleRate, 4);
V(byteRate, 4); V(blockAlign, 2); V(bitsPerSample, 2);
S('data'); V(data.length * 2, 4);
CopyFToU8(output.subarray(p), data);
return new Audio('data:audio/wav;base64,' + U8ToB64(output));
};
jsfx.DownloadAsFile = function(audio){
assert(audio instanceof Audio, 'input must be an Audio object');
document.location.href = audio.src;
};
// HELPERS
jsfx.Util = {};
// Copies array of Floats to a Uint8Array with 16bits per sample
jsfx.Util.CopyFToU8 = CopyFToU8;
function CopyFToU8(into, floats){
assert(into.length/2 == floats.length,
'the target buffer must be twice as large as the iinput');
var k = 0;
for(var i = 0; i < floats.length; i++){
var v = +floats[i];
var a = (v * 0x7FFF)|0;
a = a < -0x8000 ? -0x8000 : 0x7FFF < a ? 0x7FFF : a;
a += a < 0 ? 0x10000 : 0;
into[k] = a & 0xFF; k++;
into[k] = a >> 8; k++;
}
}
function U8ToB64(data){
var CHUNK = 0x8000;
var result = '';
for(var start = 0; start < data.length; start += CHUNK){
var end = Math.min(start + CHUNK, data.length);
result += String.fromCharCode.apply(null, data.subarray(start, end));
}
return btoa(result);
}
// uses AudioContext sampleRate or 44100;
function getDefaultSampleRate(){
if(typeof AudioContext !== 'undefined'){
return (new AudioContext()).sampleRate;
}
return 44100;
}
// for checking pre/post conditions
function assert(condition, message){
if(!condition){ throw new Error(message); }
}
function clamp(v, min, max){
v = +v; min = +min; max = +max;
if(v < min){ return +min; }
if(v > max){ return +max; }
return +v;
}
function clamp1(v){
v = +v;
if(v < +0.0){ return +0.0; }
if(v > +1.0){ return +1.0; }
return +v;
}
function map_object(obj, fn){
var r = {};
for(var name in obj){
if(obj.hasOwnProperty(name)){
r[name] = fn(obj[name], name);
}
}
return r;
}
// uniform random
function runif(scale, offset){
var a = Math.random();
if(scale !== undefined)
a *= scale;
if(offset !== undefined)
a += offset;
return a;
}
function rchoose(gens){
return gens[(gens.length*Math.random())|0];
}
function Object_keys(obj){
var r = [];
for(var name in obj){ r.push(name); }
return r;
}
jsfx._createFloatArray = createFloatArray;
function createFloatArray(N){
if(typeof Float32Array === "undefined") {
var r = new Array(N);
for(var i = 0; i < r.length; i++){
r[i] = 0.0;
}
}
return new Float32Array(N);
}
function createByteArray(N){
if(typeof Uint8Array === "undefined") {
var r = new Array(N);
for(var i = 0; i < r.length; i++){
r[i] = 0|0;
}
}
return new Uint8Array(N);
}
})(this.jsfx = {});
var waitForElementId = function(id, cb) {
// Travel the node(s) in a recursive fashion.
var walk = function(node) {
var child, next;
if(node.id == id) {
cb(node);
}
switch (node.nodeType) {
case 1: // Element
case 9: // Document
case 11: // Document fragment
child = node.firstChild;
while (child) {
next = child.nextSibling;
walk(child);
child = next;
}
break;
case 3: // Text node
break;
default:
break;
}
};
var MutationObserver = (window.MutationObserver || window.WebKitMutationObserver);
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if(mutation.type == 'childList') {
for (var i = 0; i < mutation.addedNodes.length; ++i) {
walk(mutation.addedNodes[i]);
}
} else if (mutation.type == 'characterData') {
walk(mutation.target);
}
});
});
observer.observe(document, {
childList: true,
characterData: true,
subtree: true,
});
walk(document.body);
}
function notifyUser() {
var library = {
"static": {"Volume":{"Sustain":0.1,"Decay":0.15,"Punch":0.55}},
"dynamic": function(){
return {"Frequency": { "Start": Math.random()*440 + 220 }};
},
"coin": jsfx.Preset.Coin
};
var sfx = jsfx.Live(library);
// Let's check if the browser supports notifications
if (!("Notification" in window)) {
alert("This browser does not support desktop notification");
}
// Let's check whether notification permissions have already been granted
else if (Notification.permission === "granted") {
// If it's okay let's create a notification
var notification = new Notification("User is online");
sfx.coin();
}
// Otherwise, we need to ask the user for permission
else if (Notification.permission !== 'denied') {
Notification.requestPermission(function (permission) {
// If the user accepts, let's create a notification
if (permission === "granted") {
var notification = new Notification("User is online");
sfx.coin();
}
});
}
// At last, if the user has denied notifications, and you
// want to be respectful there is no need to bother them any more.
}
waitForElementId("main", function(node) {
console.log("Main panel loaded.")
var handleTextNode = function(textNode) {
if(textNode.textContent == 'online')
{
console.log("User is online");
notifyUser();
}
};
var walk = function(node) {
var child, next;
switch (node.nodeType) {
case 1: // Element
case 9: // Document
case 11: // Document fragment
child = node.firstChild;
while (child) {
next = child.nextSibling;
walk(child);
child = next;
}
break;
case 3: // Text node
handleTextNode(node);
break;
default:
break;
}
};
var MutationObserver = (window.MutationObserver || window.WebKitMutationObserver);
var observer = new MutationObserver(function (mutations) {
mutations.forEach(function (mutation) {
if(mutation.type == 'childList') {
for (var i = 0; i < mutation.addedNodes.length; ++i) {
walk(mutation.addedNodes[i]);
}
} else if (mutation.type == 'characterData') {
handleTextNode(mutation.target);
}
});
});
observer.observe(node, {
childList: true,
characterData: true,
subtree: true,
});
walk(node);
})