Skip to content

Commit

Permalink
[v0.5.31] adds bpf and hpf, updates lpf. all use new BiQuadFilter mid…
Browse files Browse the repository at this point in the history
…dleware with configurable cutoff and q
  • Loading branch information
nnirror committed Mar 22, 2023
1 parent 03699d0 commit f51dedd
Show file tree
Hide file tree
Showing 5 changed files with 278 additions and 21 deletions.
14 changes: 12 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -324,6 +324,11 @@ You might need to activate a MIDI driver on your machine in order to send MIDI f
- `$('example').turing(16).at(0,1); // the 1st value of the 16-step Turing sequence (i.e. 0% position) is always 1`
- `$('example').turing(16).at(0.5,2); // the 9th value of the 16-step Turing sequence (i.e. 50% position) is always 2`
---
- **bpf** ( _cutoff_ = 1000, _q_ = 2.5 )
- applies a bandpass filter with configurable `cutoff` and `q` to the FacetPattern.
- example:
- `$('example').noise(n1).bpf(1000,6).gain(0.1).play(); // band-passed noise`
---
- **changed** ( )
- returns a 1 or 0 for each value in the FacetPattern. If the value is different than the previous value, returns a 1. Otherwise returns a 0. (The first value is compared against the last value in the FacetPattern.)
- example:
Expand Down Expand Up @@ -415,6 +420,11 @@ You might need to activate a MIDI driver on your machine in order to send MIDI f
- example:
- `$('example').from([0.1,0.3,0.5,0.7]).gte(0.5); // 0 0 1 1`
---
- **hpf** ( _cutoff_ = 100, _q_ = 2.5 )
- applies a high pass filter with configurable `cutoff` and `q` to the FacetPattern.
- example:
- `$('example').noise(n1).hpf(2000,6).gain(0.1).play(); // high-passed noise`
---
- **ifft** ( )
- computes the IFFT of the FacetPattern. Typically it would be used to reconstruct a FacetPattern after it had been translated into "phase data". But you can run an IFFT on any data.
- example:
Expand Down Expand Up @@ -459,9 +469,9 @@ You might need to activate a MIDI driver on your machine in order to send MIDI f
- `$('example').ramp(1,0,1000).log(100); // a logarithmic curve from 1 to 0`
---
- **lpf** ( _cutoff_ )
- applies a simple low pass filter to the FacetPattern.
- applies a low pass filter with configurable `cutoff` and `q` to the FacetPattern.
- example:
- `$('example').noise(n1).lpf(random(1,1000)); // low-passed noise`
- `$('example').noise(n1).lpf(1000,6).gain(0.1).play(); // low-passed noise`
---
- **modulo** ( _amt_ )
- returns the modulo i.e. `% amt` calculation for each value in the FacetPattern.
Expand Down
53 changes: 36 additions & 17 deletions js/FacetPattern.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const WaveFile = require('wavefile').WaveFile;
const FacetConfig = require('./config.js');
const FACET_SAMPLE_RATE = FacetConfig.settings.SAMPLE_RATE;
const curve_calc = require('./lib/curve_calc.js');
const BiQuadFilter = require('./lib/BiQuadFilter.js').BiQuadFilter;
const FFT = require('./lib/fft.js');
const { Midi, Scale } = require("tonal");
const http = require('http');
Expand Down Expand Up @@ -1163,25 +1164,43 @@ class FacetPattern {
return this;
}

lpf (cutoff) {
// copy-modded from: https://github.com/rochars/low-pass-filter/blob/master/index.js
let numChannels = 1;
let rc = 1.0 / (cutoff * 2 * Math.PI);
let dt = 1.0 / FACET_SAMPLE_RATE;
let alpha = dt / (rc + dt);
let last_val = [];
let offset;
for (let i=0; i<numChannels; i++) {
last_val[i] = this.data[i];

lpf (cutoff = 2000 , q = 2.5) {
// first argument is 0 for type = lpf. last arg is the filter gain (1)
BiQuadFilter.create(0,cutoff,FACET_SAMPLE_RATE,q,1);
let out = [];
for ( var i = 1; i < 6; i++ ) {
var v = BiQuadFilter.constants()[i-1];
v = BiQuadFilter.formatNumber(v,8);
out.push(v);
}
for (let i=0; i<this.data.length; i++) {
for (let j=0; j< numChannels; j++) {
offset = (i * numChannels) + j;
last_val[j] =
last_val[j] + (alpha * (this.data[offset] - last_val[j]));
this.data[offset] = last_val[j];
}
this.biquad(out[2],out[3],out[4],out[0],out[1]);
return this;
}

hpf (cutoff = 100, q = 2.5) {
// first argument is 1 for type = hpf. last arg is the filter gain (1)
BiQuadFilter.create(1,cutoff,FACET_SAMPLE_RATE,q,1);
let out = [];
for ( var i = 1; i < 6; i++ ) {
var v = BiQuadFilter.constants()[i-1];
v = BiQuadFilter.formatNumber(v,8);
out.push(v);
}
this.biquad(out[2],out[3],out[4],out[0],out[1]);
return this;
}

bpf (cutoff = 1000, q = 2.5) {
// first argument is 2 for type = bpf. last arg is the filter gain (1)
BiQuadFilter.create(2,cutoff,FACET_SAMPLE_RATE,q,1);
let out = [];
for ( var i = 1; i < 6; i++ ) {
var v = BiQuadFilter.constants()[i-1];
v = BiQuadFilter.formatNumber(v,8);
out.push(v);
}
this.biquad(out[2],out[3],out[4],out[0],out[1]);
return this;
}

Expand Down
228 changes: 228 additions & 0 deletions js/lib/BiQuadFilter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
// copy-modded from: https://arachnoid.com/BiQuadDesigner/index.html
/***************************************************************************
* Copyright (C) 2017, Paul Lutus *
* *
* This program is free software; you can redistribute it and/or modify *
* it under the terms of the GNU General Public License as published by *
* the Free Software Foundation; either version 2 of the License, or *
* (at your option) any later version. *
* *
* This program is distributed in the hope that it will be useful, *
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
* GNU General Public License for more details. *
* *
* You should have received a copy of the GNU General Public License *
* along with this program; if not, write to the *
* Free Software Foundation, Inc., *
* 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. *
***************************************************************************/

var BiQuadFilter = BiQuadFilter || {}


BiQuadFilter.LOWPASS = 0;
BiQuadFilter.HIGHPASS = 1;
BiQuadFilter.BANDPASS = 2;
BiQuadFilter.PEAK = 3;
BiQuadFilter.NOTCH = 4;
BiQuadFilter.LOWSHELF = 5;
BiQuadFilter.HIGHSHELF = 6;

BiQuadFilter.a0 = 0;
BiQuadFilter.a1 = 0;
BiQuadFilter.a2 = 0;
BiQuadFilter.b0 = 0;
BiQuadFilter.b1 = 0;
BiQuadFilter.b2 = 0;
BiQuadFilter.x1 = 0;
BiQuadFilter.x2 = 0;
BiQuadFilter.y1 = 0;
BiQuadFilter.y2 = 0;
BiQuadFilter.type = 0;
BiQuadFilter.center_freq = 0;
BiQuadFilter.sample_rate = 0;
BiQuadFilter.Q = 0;
BiQuadFilter.gainDB = 0;

BiQuadFilter.create = function(type, center_freq, sample_rate, Q, gainDB = 0) {
BiQuadFilter.configure(type, center_freq, sample_rate, Q, gainDB);
}

BiQuadFilter.reset = function() {
BiQuadFilter.x1 = BiQuadFilter.x2 = BiQuadFilter.y1 = BiQuadFilter.y2 = 0;
}

BiQuadFilter.frequency = function() {
return BiQuadFilter.center_freq;
}

BiQuadFilter.configure = function(type,center_freq,sample_rate, Q, gainDB) {
BiQuadFilter.functions = [
BiQuadFilter.f_lowpass,
BiQuadFilter.f_highpass,
BiQuadFilter.f_bandpass,
BiQuadFilter.f_peak,
BiQuadFilter.f_notch,
BiQuadFilter.f_lowshelf,
BiQuadFilter.f_highshelf
];
BiQuadFilter.reset();
BiQuadFilter.Q = (Q == 0) ? 1e-9 : Q;
BiQuadFilter.type = type;
BiQuadFilter.sample_rate = sample_rate;
BiQuadFilter.gainDB = gainDB;
BiQuadFilter.reconfigure(center_freq);
}

// allow parameter change while running
BiQuadFilter.reconfigure = function(cf) {
BiQuadFilter.center_freq = cf;
// only used for peaking and shelving filters
var gain_abs = Math.pow(10, BiQuadFilter.gainDB / 40);
var omega = 2 * Math.PI * cf / BiQuadFilter.sample_rate;
var sn = Math.sin(omega);
var cs = Math.cos(omega);
var alpha = sn / (2 * BiQuadFilter.Q);
var beta = Math.sqrt(gain_abs + gain_abs);

// call the corresponding setup function
BiQuadFilter.functions[BiQuadFilter.type](gain_abs,omega,sn,cs,alpha,beta);

// by prescaling filter constants, eliminate one variable
BiQuadFilter.b0 /= BiQuadFilter.a0;
BiQuadFilter.b1 /= BiQuadFilter.a0;
BiQuadFilter.b2 /= BiQuadFilter.a0;
BiQuadFilter.a1 /= BiQuadFilter.a0;
BiQuadFilter.a2 /= BiQuadFilter.a0;
}

BiQuadFilter.f_bandpass = function(gain_abs,omega,sn,cs,alpha,beta) {
BiQuadFilter.b0 = alpha;
BiQuadFilter.b1 = 0;
BiQuadFilter.b2 = -alpha;
BiQuadFilter.a0 = 1 + alpha;
BiQuadFilter.a1 = -2 * cs;
BiQuadFilter.a2 = 1 - alpha;
}

BiQuadFilter.f_lowpass = function(gain_abs,omega,sn,cs,alpha,beta) {
BiQuadFilter.b0 = (1 - cs) / 2;
BiQuadFilter.b1 = 1 - cs;
BiQuadFilter.b2 = (1 - cs) / 2;
BiQuadFilter.a0 = 1 + alpha;
BiQuadFilter.a1 = -2 * cs;
BiQuadFilter.a2 = 1 - alpha;
}

BiQuadFilter.f_highpass = function(gain_abs,omega,sn,cs,alpha,beta) {
BiQuadFilter.b0 = (1 + cs) / 2;
BiQuadFilter.b1 = -(1 + cs);
BiQuadFilter.b2 = (1 + cs) / 2;
BiQuadFilter.a0 = 1 + alpha;
BiQuadFilter.a1 = -2 * cs;
BiQuadFilter.a2 = 1 - alpha;
}

BiQuadFilter.f_notch = function(gain_abs,omega,sn,cs,alpha,beta) {
BiQuadFilter.b0 = 1;
BiQuadFilter.b1 = -2 * cs;
BiQuadFilter.b2 = 1;
BiQuadFilter.a0 = 1 + alpha;
BiQuadFilter.a1 = -2 * cs;
BiQuadFilter.a2 = 1 - alpha;
}

BiQuadFilter.f_peak = function(gain_abs,omega,sn,cs,alpha,beta) {
BiQuadFilter.b0 = 1 + (alpha * gain_abs);
BiQuadFilter.b1 = -2 * cs;
BiQuadFilter.b2 = 1 - (alpha * gain_abs);
BiQuadFilter.a0 = 1 + (alpha / gain_abs);
BiQuadFilter.a1 = -2 * cs;
BiQuadFilter.a2 = 1 - (alpha / gain_abs);
}

BiQuadFilter.f_lowshelf = function(gain_abs,omega,sn,cs,alpha,beta) {
BiQuadFilter.b0 = gain_abs * ((gain_abs + 1) - (gain_abs - 1) * cs + beta * sn);
BiQuadFilter.b1 = 2 * gain_abs * ((gain_abs - 1) - (gain_abs + 1) * cs);
BiQuadFilter.b2 = gain_abs * ((gain_abs + 1) - (gain_abs - 1) * cs - beta * sn);
BiQuadFilter.a0 = (gain_abs + 1) + (gain_abs - 1) * cs + beta * sn;
BiQuadFilter.a1 = -2 * ((gain_abs - 1) + (gain_abs + 1) * cs);
BiQuadFilter.a2 = (gain_abs + 1) + (gain_abs - 1) * cs - beta * sn;
}

BiQuadFilter.f_highshelf = function(gain_abs,omega,sn,cs,alpha,beta) {
BiQuadFilter.b0 = gain_abs * ((gain_abs + 1) + (gain_abs - 1) * cs + beta * sn);
BiQuadFilter.b1 = -2 * gain_abs * ((gain_abs - 1) + (gain_abs + 1) * cs);
BiQuadFilter.b2 = gain_abs * ((gain_abs + 1) + (gain_abs - 1) * cs - beta * sn);
BiQuadFilter.a0 = (gain_abs + 1) - (gain_abs - 1) * cs + beta * sn;
BiQuadFilter.a1 = 2 * ((gain_abs - 1) - (gain_abs + 1) * cs);
BiQuadFilter.a2 = (gain_abs + 1) - (gain_abs - 1) * cs - beta * sn;
}

// provide a static amplitude result for testing
BiQuadFilter.result = function(f) {
var phi = Math.pow((Math.sin(2.0 * Math.PI * f / (2.0 * BiQuadFilter.sample_rate))), 2.0);
var r = (Math.pow(BiQuadFilter.b0 + BiQuadFilter.b1 + BiQuadFilter.b2, 2.0) - 4.0 * (BiQuadFilter.b0 * BiQuadFilter.b1 + 4.0 * BiQuadFilter.b0 * BiQuadFilter.b2 + BiQuadFilter.b1 * BiQuadFilter.b2) * phi + 16.0 * BiQuadFilter.b0 * BiQuadFilter.b2 * phi * phi) / (Math.pow(1.0 + BiQuadFilter.a1 + BiQuadFilter.a2, 2.0) - 4.0 * (BiQuadFilter.a1 + 4.0 * BiQuadFilter.a2 + BiQuadFilter.a1 * BiQuadFilter.a2) * phi + 16.0 * BiQuadFilter.a2 * phi * phi);
r = (r < 0)?0:r;
return Math.sqrt(r);
}

// provide a static decibel result for testing
BiQuadFilter.log_result = function(f) {
var r;
try {
r = 20 * Math.log10(BiQuadFilter.result(f));
}
catch (e) {
//console.log(e);
r = -100;
}
if(!isFinite(r) || isNaN(r)) {
r = -100;
}
return r;
}

// return the constant set for this filter
BiQuadFilter.constants = function() {
return [BiQuadFilter.a1, BiQuadFilter.a2,BiQuadFilter.b0, BiQuadFilter.b1, BiQuadFilter.b2];
}

// perform one filtering step
BiQuadFilter.filter = function(x) {
var y = BiQuadFilter.b0 * x + BiQuadFilter.b1 * BiQuadFilter.x1 + BiQuadFilter.b2 * BiQuadFilter.x2 - BiQuadFilter.a1 * BiQuadFilter.y1 - BiQuadFilter.a2 * BiQuadFilter.y2;
BiQuadFilter.x2 = BiQuadFilter.x1;
BiQuadFilter.x1 = BiQuadFilter.x;
BiQuadFilter.y2 = BiQuadFilter.y1;
BiQuadFilter.y1 = y;
return (y);
}

BiQuadFilter.formatNumber = function(n,p) {
return n.toFixed(p);
}

module.exports = {
BiQuadFilter: BiQuadFilter
};

// filter type, cutoff, sr, q, gain
// BiQuadFilter.create(0,100,44100,2.5,1);
// let lemma = [];
// let out = [];
// for(var i = 1; i < 6;i++) {
// var v = BiQuadFilter.constants()[i-1]; // contains [a1,a2,b0,b1,b2]
// v = BiQuadFilter.formatNumber(v,8);
// lemma.push(v);
// }
// out[0] = lemma[2];
// out[1] = lemma[3];
// out[2] = lemma[4];
// out[3] = lemma[0];
// out[4] = lemma[1];
// console.log(out);
// // the numbers in out can go directly into biquad.
// // all you need to do is create different functions that are for filter type: bandpass = 2, lowpass = 0, highpass = 1 to start
// // then you send the sample rate constant and the gain as 1
// // and it's only cutoff and q that are the arguments.
2 changes: 1 addition & 1 deletion package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "facet",
"version": "0.5.30",
"version": "0.5.31",
"description": "Facet is a live coding system for algorithmic music",
"main": "index.js",
"directories": {
Expand Down

0 comments on commit f51dedd

Please sign in to comment.