gibberish-dsp
Version:
Gibberish is designed to be an optimized API for audio synthesis using per-sample techniques.
209 lines (168 loc) • 7.16 kB
JavaScript
const g = require( 'genish.js' ),
filter = require( './filter.js' )
const genish = g
module.exports = function( Gibberish ) {
Gibberish.genish.diodeZDF = ( input, __Q, __freq, saturation, isStereo=false ) => {
const iT = 1 / g.gen.samplerate,
kz1 = g.history(0),
kz2 = g.history(0),
kz3 = g.history(0),
kz4 = g.history(0)
let ka1 = 1.0,
ka2 = 0.5,
ka3 = 0.5,
ka4 = 0.5,
kindx = 0
const freq = g.mul( g.max(.005, g.min( __freq, .995)), genish.gen.samplerate / 2 )
//const freq = g.max(.005, g.min( __freq, .995))
// XXX this is where the magic number hapens for Q...
const Q = g.memo( g.add( .5, g.mul( __Q, g.add( 5, g.sub( 5, g.mul( g.div( freq, 20000 ), 5 ) ) ) ) ) )
// kwd = 2 * $M_PI * acf[kindx]
const kwd = g.memo( g.mul( Math.PI * 2, freq ) )
// kwa = (2/iT) * tan(kwd * iT/2)
const kwa =g.memo( g.mul( 2/iT, g.tan( g.mul( kwd, iT/2 ) ) ) )
// kG = kwa * iT/2
const kg = g.memo( g.mul( kwa, iT/2 ) )
const kG4 = g.memo( g.mul( .5, g.div( kg, g.add( 1, kg ) ) ) )
const kG3 = g.memo( g.mul( .5, g.div( kg, g.sub( g.add( 1, kg ), g.mul( g.mul( .5, kg ), kG4 ) ) ) ) )
const kG2 = g.memo( g.mul( .5, g.div( kg, g.sub( g.add( 1, kg ), g.mul( g.mul( .5, kg ), kG3 ) ) ) ) )
const kG1 = g.memo( g.div( kg, g.sub( g.add( 1, kg ), g.mul( kg, kG2 ) ) ) )
const kGAMMA = g.memo( g.mul( g.mul( kG4, kG3 ) , g.mul( kG2, kG1 ) ) )
const kSG1 = g.memo( g.mul( g.mul( kG4, kG3 ), kG2 ) )
const kSG2 = g.memo( g.mul( kG4, kG3) )
const kSG3 = kG4
let kSG4 = 1.0
// kk = 4.0*(kQ - 0.5)/(25.0 - 0.5)
const kalpha = g.memo( g.div( kg, g.add(1.0, kg) ) )
const kbeta1 = g.memo( g.div( 1.0, g.sub( g.add( 1, kg ), g.mul( kg, kG2 ) ) ) )
const kbeta2 = g.memo( g.div( 1.0, g.sub( g.add( 1, kg ), g.mul( g.mul( .5, kg ), kG3 ) ) ) )
const kbeta3 = g.memo( g.div( 1.0, g.sub( g.add( 1, kg ), g.mul( g.mul( .5, kg ), kG4 ) ) ) )
const kbeta4 = g.memo( g.div( 1.0, g.add( 1, kg ) ) )
const kgamma1 = g.memo( g.add( 1, g.mul( kG1, kG2 ) ) )
const kgamma2 = g.memo( g.add( 1, g.mul( kG2, kG3 ) ) )
const kgamma3 = g.memo( g.add( 1, g.mul( kG3, kG4 ) ) )
const kdelta1 = kg
const kdelta2 = g.memo( g.mul( 0.5, kg ) )
const kdelta3 = g.memo( g.mul( 0.5, kg ) )
const kepsilon1 = kG2
const kepsilon2 = kG3
const kepsilon3 = kG4
const klastcut = freq
//;; feedback inputs
const kfb4 = g.memo( g.mul( kbeta4 , kz4.out ) )
const kfb3 = g.memo( g.mul( kbeta3, g.add( kz3.out, g.mul( kfb4, kdelta3 ) ) ) )
const kfb2 = g.memo( g.mul( kbeta2, g.add( kz2.out, g.mul( kfb3, kdelta2 ) ) ) )
//;; feedback process
const kfbo1 = g.memo( g.mul( kbeta1, g.add( kz1.out, g.mul( kfb2, kdelta1 ) ) ) )
const kfbo2 = g.memo( g.mul( kbeta2, g.add( kz2.out, g.mul( kfb3, kdelta2 ) ) ) )
const kfbo3 = g.memo( g.mul( kbeta3, g.add( kz3.out, g.mul( kfb4, kdelta3 ) ) ) )
const kfbo4 = kfb4
const kSIGMA = g.memo(
g.add(
g.add(
g.mul( kSG1, kfbo1 ),
g.mul( kSG2, kfbo2 )
),
g.add(
g.mul( kSG3, kfbo3 ),
g.mul( kSG4, kfbo4 )
)
)
)
//const kSIGMA = 1
//;; non-linear processing
//if (knlp == 1) then
// kin = (1.0 / tanh(ksaturation)) * tanh(ksaturation * kin)
//elseif (knlp == 2) then
// kin = tanh(ksaturation * kin)
//endif
//
//const kin = input
let kin = isStereo === true ? g.add( input[0], input[1] ) : input//g.memo( g.mul( g.div( 1, g.tanh( saturation ) ), g.tanh( g.mul( saturation, input ) ) ) )
kin = g.tanh( g.mul( saturation, kin ) )
const kun = g.div( g.sub( kin, g.mul( Q, kSIGMA ) ), g.add( 1, g.mul( Q, kGAMMA ) ) )
//const kun = g.div( 1, g.add( 1, g.mul( Q, kGAMMA ) ) )
//(kin - kk * kSIGMA) / (1.0 + kk * kGAMMA)
//;; 1st stage
let kxin = g.memo( g.add( g.add( g.mul( kun, kgamma1 ), kfb2), g.mul( kepsilon1, kfbo1 ) ) )
// (kun * kgamma1 + kfb2 + kepsilon1 * kfbo1)
let kv = g.memo( g.mul( g.sub( g.mul( ka1, kxin ), kz1.out ), kalpha ) )
//kv = (ka1 * kxin - kz1) * kalpha
let klp = g.add( kv, kz1.out )
//klp = kv + kz1
kz1.in( g.add( klp, kv ) )
//kz1 = klp + kv
//;; 2nd stage
//kxin = (klp * kgamma2 + kfb3 + kepsilon2 * kfbo2)
//kv = (ka2 * kxin - kz2) * kalpha
//klp = kv + kz2
//kz2 = klp + kv
kxin = g.memo( g.add( g.add( g.mul( klp, kgamma2 ), kfb3), g.mul( kepsilon2, kfbo2 ) ) )
// (kun * kgamma1 + kfb2 + kepsilon1 * kfbo1)
kv = g.memo( g.mul( g.sub( g.mul( ka2, kxin ), kz2.out ), kalpha ) )
//kv = (ka1 * kxin - kz1) * kalpha
klp = g.add( kv, kz2.out )
//klp = kv + kz1
kz2.in( g.add( klp, kv ) )
//kz1 = klp + kv
//;; 3rd stage
//kxin = (klp * kgamma3 + kfb4 + kepsilon3 * kfbo3)
//kv = (ka3 * kxin - kz3) * kalpha
//klp = kv + kz3
//kz3 = klp + kv
kxin = g.memo( g.add( g.add( g.mul( klp, kgamma3 ), kfb4), g.mul( kepsilon3, kfbo3 ) ) )
// (kun * kgamma1 + kfb2 + kepsilon1 * kfbo1)
kv = g.memo( g.mul( g.sub( g.mul( ka3, kxin ), kz3.out ), kalpha ) )
//kv = (ka1 * kxin - kz1) * kalpha
klp = g.add( kv, kz3.out )
//klp = kv + kz1
kz3.in( g.add( klp, kv ) )
//kz1 = klp + kv
//;; 4th stage
//kv = (ka4 * klp - kz4) * kalpha
//klp = kv + kz4
//kz4 = klp + kv
// (kun * kgamma1 + kfb2 + kepsilon1 * kfbo1)
kv = g.memo( g.mul( g.sub( g.mul( ka4, kxin ), kz4.out ), kalpha ) )
//kv = (ka1 * kxin - kz1) * kalpha
klp = g.add( kv, kz4.out )
//klp = kv + kz1
kz4.in( g.add( klp, kv ) )
//kz1 = klp + kv
if( isStereo ) {
//let polesR = g.data([ 0,0,0,0 ], 1, { meta:true }),
// rezzR = g.clamp( g.mul( polesR[3], rez ) ),
// outputR = g.sub( input[1], rezzR )
//polesR[0] = g.add( polesR[0], g.mul( g.add( g.mul(-1, polesR[0] ), outputR ), cutoff ))
//polesR[1] = g.add( polesR[1], g.mul( g.add( g.mul(-1, polesR[1] ), polesR[0] ), cutoff ))
//polesR[2] = g.add( polesR[2], g.mul( g.add( g.mul(-1, polesR[2] ), polesR[1] ), cutoff ))
//polesR[3] = g.add( polesR[3], g.mul( g.add( g.mul(-1, polesR[3] ), polesR[2] ), cutoff ))
//let right = g.switch( isLowPass, polesR[3], g.sub( outputR, polesR[3] ) )
//returnValue = [left, right]
}else{
// returnValue = klp
}
//returnValue = klp
return klp
}
const DiodeZDF = inputProps => {
const zdf = Object.create( filter )
const props = Object.assign( {}, DiodeZDF.defaults, filter.defaults, inputProps )
const isStereo = props.input.isStereo
Object.assign( zdf, props )
const __out = Gibberish.factory(
zdf,
Gibberish.genish.diodeZDF( g.in('input'), g.in('Q'), g.in('cutoff'), g.in('saturation'), isStereo ),
['filters','Filter24TB303'],
props
)
return __out
}
DiodeZDF.defaults = {
input:0,
Q: .65,
saturation: 1,
cutoff:.5
}
return DiodeZDF
}