Commit 0431a6bb authored by Jonathan Wilkes's avatar Jonathan Wilkes
Browse files

add bob~ lib to extra (accidentally forgot to add it as part of the Vanilla backport)

parent 7c520244
## Makefile.am -- Process this file with automake to produce Makefile.in
NAME=bob~
external_LTLIBRARIES = bob~.la
SOURCES = bob~.c
PATCHES = bob~-help.pd output~.pd
OTHERDATA =
EXTRA_DIST = makefile
###############################
# you shouldn't need to add anything below here
dist_external_DATA = $(PATCHES) $(OTHERDATA)
AUTOMAKE_OPTIONS = foreign
AM_CPPFLAGS = -I$(top_srcdir)/src -DPD
AM_CFLAGS = @ARCH_CFLAGS@
AM_LIBS = $(LIBM)
AM_LDFLAGS = -module -avoid-version -shared @ARCH_LDFLAGS@ -shrext .@EXTERNAL_EXTENSION@ -L$(top_srcdir)/src
externaldir = $(pkglibdir)/extra/$(NAME)
if MINGW
AM_LIBS += -lpd
endif
libtool: $(LIBTOOL_DEPS)
$(SHELL) ./config.status --recheck
The bob~ object. BSD licensed; Copyright notice is in bob~ source code.
Imitates a Moog resonant filter by Runge-Kutte numerical integration of
a differential equation approximately describing the dynamics of the circuit.
Useful references:
Tim Stilson
Analyzing the Moog VCF with Considerations for Digital Implementation
https://ccrma.stanford.edu/~stilti/papers/moogvcf.ps.gz
(sections 1 and 2 are a reasonably good introduction but the model they use
is highly idealized.)
Timothy E. Stinchcombe
Analysis of the Moog Transistor Ladder and Derivative Filters
(long, but a very thorough description of how the filter works including
its nonlinearities)
Antti Huovilainen
Non-linear digital implementation of the moog ladder filter
(comes close to giving a differential equation for a reasonably realistic
model of the filter).
Th differential equations are:
y1' = k * (S(x - r * y4) - S(y1))
y2' = k * (S(y1) - S(y2))
y3' = k * (S(y2) - S(y3))
y4' = k * (S(y3) - S(y4))
where k controls the cutoff frequency, r is feedback (<= 4 for
stability), and S(x) is a saturation function.
#N canvas 27 58 1062 722 12;
#X obj 231 347 env~ 8192, f 4;
#X floatatom 230 387 5 0 0 0 - - -, f 5;
#X floatatom 408 193 5 0 200 0 - - -, f 5;
#X obj 39 260 env~ 8192, f 5;
#X floatatom 39 300 5 0 0 0 - - -, f 5;
#X obj 279 317 bob~;
#X obj 408 215 / 25;
#X msg 950 229 print;
#X obj 87 259 output~;
#X floatatom 291 179 5 0 150 0 - - -, f 5;
#X obj 291 201 mtof;
#X obj 292 246 pack 0 50;
#X obj 292 271 line~;
#X msg 886 227 clear;
#X obj 280 349 output~;
#X floatatom 291 224 7 0 0 0 - - -, f 7;
#X floatatom 611 177 5 0 999 0 - - -, f 5;
#X msg 611 223 saturation \$1;
#X obj 611 127 loadbang;
#X obj 611 199 / 100;
#X text 885 183 clear or print;
#X text 889 202 filter state;
#X floatatom 748 197 5 1 10 0 - - -, f 5;
#X text 744 122 oversampling;
#X msg 748 224 oversample \$1;
#X text 419 88 "resonance";
#X text 418 105 (>4 to oscillate);
#X obj 748 145 loadbang;
#X msg 748 170 2;
#X text 456 211 scaled to 0-8;
#X text 455 193 0-200 control;
#X text 263 86 resonant or cutoff frequency, f 16;
#X text 300 60 ----- filter parameters ----;
#X text 609 59 ------ optimizations / setup params -------;
#X text 899 161 debugging:;
#X text 603 88 saturation point;
#X text 600 105 of "transistors";
#X msg 611 152 300;
#X obj 408 142 loadbang;
#X msg 408 167 10;
#X text 521 625 "Clear" momentarily shorts out the capacitors in case
the filter has gone unstable and stopped working.;
#X text 523 410 By default bob~ does one step of 4th-order Runge-Kutte
integration per audio sample. This works OK for resonant/cutoff frequencies
up to about 1/2 Nyquist. To improve accuracy and/or to extend the range
of the filter to higher cutoff frequencies you can oversample by any
factor - but note that computation time rises accordingly. At high
cutoff frequencies/resonance values the RK approximation can go unstable.
You can combat this by raising the oversampling factor.;
#X obj 407 243 line~;
#N canvas 743 303 450 300 test 0;
#X obj 102 122 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
1;
#X obj 313 127 min~;
#X obj 357 103 -~ 1;
#X obj 357 128 *~ -50;
#X floatatom 102 102 5 0 128 0 - - -, f 5;
#X obj 235 72 mtof;
#X obj 102 141 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 1
1;
#X text 196 32 test signal;
#X text 147 102 pitch;
#X text 119 140 sawtooth;
#X obj 312 70 phasor~ 220;
#X obj 233 107 osc~ 220;
#X obj 102 160 tgl 15 0 empty empty empty 17 7 0 10 -262144 -1 -1 0
1;
#X text 121 120 sine;
#X text 120 159 noise;
#X floatatom 103 181 3 0 100 0 - - -, f 3;
#X obj 62 24 loadbang;
#X msg 62 49 57;
#X msg 48 163 80;
#X text 133 180 dB out;
#X msg 60 111 1;
#X obj 362 189 noise~;
#X obj 204 207 *~ 0;
#X obj 316 184 *~ 0;
#X obj 315 217 *~ 0;
#X obj 208 238 +~;
#X obj 208 263 outlet~;
#X connect 0 0 22 1;
#X connect 1 0 23 0;
#X connect 2 0 3 0;
#X connect 3 0 1 1;
#X connect 4 0 5 0;
#X connect 5 0 10 0;
#X connect 5 0 11 0;
#X connect 6 0 23 1;
#X connect 10 0 1 0;
#X connect 10 0 2 0;
#X connect 11 0 22 0;
#X connect 12 0 24 1;
#X connect 16 0 17 0;
#X connect 16 0 18 0;
#X connect 16 0 20 0;
#X connect 17 0 4 0;
#X connect 18 0 15 0;
#X connect 20 0 6 0;
#X connect 21 0 24 0;
#X connect 22 0 25 0;
#X connect 23 0 25 1;
#X connect 24 0 25 1;
#X connect 25 0 26 0;
#X coords 0 -1 1 1 95 100 2 100 100;
#X restore 101 96 pd test;
#X text 228 410 output monitor;
#X text 35 321 input monitor;
#X obj 291 130 loadbang;
#X msg 291 155 69;
#X text 47 65 ----- test input ----;
#X text 356 366 <--- adjust output "dB" to hear filter output.;
#X text 21 558 The design is based on papers by Tim Stilson \, Timothy
E. Stinchcombe \, and Antti Huovilainen. See README.txt for pointers.
;
#X text 23 459 The three audio inputs are the signal to filter \, the
cutoff/resonant frequency in cycles per second \, and "resonance" (the
sharpness of the filter). Nominally \, a resonance of 4 should be the
limit of stability -- above that \, the filter oscillates.;
#X text 24 10 bob~ - Runge-Kutte numerical simulation of the Moog analog
resonant filter, f 79;
#X text 876 676 updated for Pd 0.47;
#X text 522 565 The saturation parameter determines at what signal
level the "transistors" in the model saturate. The maximum output amplitude
is about 2/3 of that value.;
#X connect 0 0 1 0;
#X connect 2 0 6 0;
#X connect 3 0 4 0;
#X connect 5 0 0 0;
#X connect 5 0 14 0;
#X connect 5 0 14 1;
#X connect 6 0 42 0;
#X connect 7 0 5 0;
#X connect 9 0 10 0;
#X connect 10 0 15 0;
#X connect 11 0 12 0;
#X connect 12 0 5 1;
#X connect 13 0 5 0;
#X connect 15 0 11 0;
#X connect 16 0 19 0;
#X connect 17 0 5 0;
#X connect 18 0 37 0;
#X connect 19 0 17 0;
#X connect 22 0 24 0;
#X connect 24 0 5 0;
#X connect 27 0 28 0;
#X connect 28 0 22 0;
#X connect 37 0 16 0;
#X connect 38 0 39 0;
#X connect 39 0 2 0;
#X connect 42 0 5 2;
#X connect 43 0 8 0;
#X connect 43 0 8 1;
#X connect 43 0 3 0;
#X connect 43 0 5 0;
#X connect 46 0 47 0;
#X connect 47 0 9 0;
/* bob~ - use a differential equation solver to imitate an analogue circuit */
/* copyright 2015 Miller Puckette - BSD license */
#include "m_pd.h"
#include <math.h>
#define DIM 4
#define FLOAT double
/* if CALCERROR is defined we compute an error estaimate to verify
the filter, outputting it from a second outlet on demand. This
doubles the computation time, so it's only compiled in for testing. */
/* #define CALCERROR */
typedef struct _params
{
FLOAT p_input;
FLOAT p_cutoff;
FLOAT p_resonance;
FLOAT p_saturation;
FLOAT p_derivativeswere[DIM];
} t_params;
/* imitate the (tanh) clipping function of a transistor pair. We
hope/assume the C compiler is smart enough to inline this so use
a function instead of a #define. */
#if 0
static FLOAT clip(FLOAT value, FLOAT saturation, FLOAT saturationinverse)
{
return (saturation * tanh(value * saturationinverse));
}
#else
/* cheaper way - to 4th order, tanh is x - x*x*x/3; this cubic's
plateaus are at +/- 1 so clip to 1 and evaluate the cubic.
This is pretty coarse - for instance if you clip a sinusoid this way you
can sometimes hear the discontinuity in 4th derivative at the clip point */
static FLOAT clip(FLOAT value, FLOAT saturation, FLOAT saturationinverse)
{
float v2 = (value*saturationinverse > 1 ? 1 :
(value*saturationinverse < -1 ? -1:
value*saturationinverse));
return (saturation * (v2 - (1./3.) * v2 * v2 * v2));
}
#endif
static void calc_derivatives(FLOAT *dstate, FLOAT *state, t_params *params)
{
FLOAT k = ((float)(2*3.14159)) * params->p_cutoff;
FLOAT sat = params->p_saturation, satinv = 1./sat;
FLOAT satstate0 = clip(state[0], sat, satinv);
FLOAT satstate1 = clip(state[1], sat, satinv);
FLOAT satstate2 = clip(state[2], sat, satinv);
dstate[0] = k *
(clip(params->p_input - params->p_resonance * state[3], sat, satinv)
- satstate0);
dstate[1] = k * (satstate0 - satstate1);
dstate[2] = k * (satstate1 - satstate2);
dstate[3] = k * (satstate2 - clip(state[3], sat, satinv));
}
static void solver_euler(FLOAT *state, FLOAT *errorestimate,
FLOAT stepsize, t_params *params)
{
FLOAT cumerror = 0;
int i;
FLOAT derivatives[DIM];
calc_derivatives(derivatives, state, params);
*errorestimate = 0;
for (i = 0; i < DIM; i++)
{
state[i] += stepsize * derivatives[i];
*errorestimate += (derivatives[i] > params->p_derivativeswere[i] ?
derivatives[i] - params->p_derivativeswere[i] :
params->p_derivativeswere[i] - derivatives[i]);
}
for (i = 0; i < DIM; i++)
params->p_derivativeswere[i] = derivatives[i];
}
static void solver_rungekutte(FLOAT *state, FLOAT *errorestimate,
FLOAT stepsize, t_params *params)
{
FLOAT cumerror = 0;
int i;
FLOAT deriv1[DIM], deriv2[DIM], deriv3[DIM], deriv4[DIM], tempstate[DIM];
FLOAT oldstate[DIM], backstate[DIM];
#if CALCERROR
for (i = 0; i < DIM; i++)
oldstate[i] = state[i];
#endif
*errorestimate = 0;
calc_derivatives(deriv1, state, params);
for (i = 0; i < DIM; i++)
tempstate[i] = state[i] + 0.5 * stepsize * deriv1[i];
calc_derivatives(deriv2, tempstate, params);
for (i = 0; i < DIM; i++)
tempstate[i] = state[i] + 0.5 * stepsize * deriv2[i];
calc_derivatives(deriv3, tempstate, params);
for (i = 0; i < DIM; i++)
tempstate[i] = state[i] + stepsize * deriv3[i];
calc_derivatives(deriv4, tempstate, params);
for (i = 0; i < DIM; i++)
state[i] += (1./6.) * stepsize *
(deriv1[i] + 2 * deriv2[i] + 2 * deriv3[i] + deriv4[i]);
#if CALCERROR
calc_derivatives(deriv1, state, params);
for (i = 0; i < DIM; i++)
tempstate[i] = state[i] - 0.5 * stepsize * deriv1[i];
calc_derivatives(deriv2, tempstate, params);
for (i = 0; i < DIM; i++)
tempstate[i] = state[i] - 0.5 * stepsize * deriv2[i];
calc_derivatives(deriv3, tempstate, params);
for (i = 0; i < DIM; i++)
tempstate[i] = state[i] - stepsize * deriv3[i];
calc_derivatives(deriv4, tempstate, params);
for (i = 0; i < DIM; i++)
{
backstate[i] = state[i ]- (1./6.) * stepsize *
(deriv1[i] + 2 * deriv2[i] + 2 * deriv3[i] + deriv4[i]);
*errorestimate += (backstate[i] > oldstate[i] ?
backstate[i] - oldstate[i] : oldstate[i] - backstate[i]);
}
#endif
}
typedef struct _bob
{
t_object x_obj;
t_float x_f;
t_outlet *x_out1; /* signal output */
#ifdef CALCERROR
t_outlet *x_out2; /* error estimate */
FLOAT x_cumerror;
#endif
t_params x_params;
FLOAT x_state[DIM];
FLOAT x_sr;
int x_oversample;
int x_errorcount;
} t_bob;
static t_class *bob_class;
static void bob_saturation(t_bob *x, t_float saturation)
{
if (saturation <= 1e-3)
saturation = 1e-3;
x->x_params.p_saturation = saturation;
}
static void bob_oversample(t_bob *x, t_float oversample)
{
if (oversample <= 1)
oversample = 1;
x->x_oversample = oversample;
}
static void bob_clear(t_bob *x)
{
int i;
for (i = 0; i < DIM; i++)
x->x_state[i] = x->x_params.p_derivativeswere[i] = 0;
}
static void bob_error(t_bob *x)
{
#ifdef CALCERROR
outlet_float(x->x_out2,
(x->x_errorcount ? x->x_cumerror/x->x_errorcount : 0));
x->x_cumerror = 0;
x->x_errorcount = 0;
#else
post("error estimate unavailable (not compiled in)");
#endif
}
static void bob_print(t_bob *x)
{
int i;
for (i = 0; i < DIM; i++)
post("state %d: %f", i, x->x_state[i]);
post("saturation %f", x->x_params.p_saturation);
post("oversample %d", x->x_oversample);
}
static void *bob_new( void)
{
t_bob *x = (t_bob *)pd_new(bob_class);
x->x_out1 = outlet_new(&x->x_obj, gensym("signal"));
inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
inlet_new(&x->x_obj, &x->x_obj.ob_pd, &s_signal, &s_signal);
x->x_f = 0;
bob_clear(x);
bob_saturation(x, 3);
bob_oversample(x, 2);
#ifdef CALCERROR
x->x_cumerror = 0;
x->x_errorcount = 0;
x->x_out2 = outlet_new(&x->x_obj, gensym("float"));
#endif
return (x);
}
static t_int *bob_perform(t_int *w)
{
t_bob *x = (t_bob *)(w[1]);
t_float *in1 = (t_float *)(w[2]);
t_float *cutoffin = (t_float *)(w[3]);
t_float *resonancein = (t_float *)(w[4]);
t_float *out = (t_float *)(w[5]);
int n = (int)(w[6]), i, j;
FLOAT stepsize = 1./(x->x_oversample * x->x_sr);
FLOAT errorestimate;
for (i = 0; i < n; i++)
{
x->x_params.p_input = *in1++;
x->x_params.p_cutoff = *cutoffin++;
if ((x->x_params.p_resonance = *resonancein++) < 0)
x->x_params.p_resonance = 0;
for (j = 0; j < x->x_oversample; j++)
solver_rungekutte(x->x_state, &errorestimate,
stepsize, &x->x_params);
*out++ = x->x_state[0];
#if CALCERROR
x->x_cumerror += errorestimate;
x->x_errorcount++;
#endif
}
return (w+7);
}
static void bob_dsp(t_bob *x, t_signal **sp)
{
x->x_sr = sp[0]->s_sr;
dsp_add(bob_perform, 6, x, sp[0]->s_vec, sp[1]->s_vec,
sp[2]->s_vec, sp[3]->s_vec, sp[0]->s_n);
}
void bob_tilde_setup(void)
{
int i;
bob_class = class_new(gensym("bob~"),
(t_newmethod)bob_new, 0, sizeof(t_bob), 0, 0);
class_addmethod(bob_class, (t_method)bob_saturation, gensym("saturation"),
A_FLOAT, 0);
class_addmethod(bob_class, (t_method)bob_oversample, gensym("oversample"),
A_FLOAT, 0);
class_addmethod(bob_class, (t_method)bob_clear, gensym("clear"), 0);
class_addmethod(bob_class, (t_method)bob_print, gensym("print"), 0);
class_addmethod(bob_class, (t_method)bob_error, gensym("error"), 0);
class_addmethod(bob_class, (t_method)bob_dsp, gensym("dsp"), A_CANT, 0);
CLASS_MAINSIGNALIN(bob_class, t_bob, x_f);
}
NAME=bob~
CSYM=bob_tilde
include ../makefile.subdir
#N canvas 96 14 463 390 10;
#X obj 12 110 hsl 63 18 0.01 1 1 0 \$0-v \$0-v volume 20 10 1 9 -245500
-13381 -1 150 0;
#X obj 80 92 tgl 18 0 THIS_IS_HERE_TO_GET_RID_OF_THE_OUTLET \$0-dsp-toggle
dsp 2 9 1 9 -225271 -195568 -33289 1 1;
#N canvas 366 412 482 356 dsp 0;
#X obj 11 7 inlet;
#X obj 92 226 select 0 1;
#X msg 125 248 6;
#X obj 92 57 route dsp;
#X obj 92 36 receive pd;
#X obj 206 138 loadbang;
#X msg 11 220 dsp \$1;
#X obj 11 245 send pd;
#X msg 206 278 set \$1;
#X obj 206 174 value GLOBAL_PDDP_DSP;
#X msg 109 278 color \$1 20 12;
#X obj 180 309 send \$0-dsp-toggle;
#X obj 92 115 change;
#X msg 92 247 0;
#X connect 0 0 6 0;
#X connect 0 0 12 0;
#X connect 1 0 13 0;
#X connect 1 1 2 0;
#X connect 2 0 10 0;
#X connect 3 0 12 0;
#X connect 4 0 3 0;
#X connect 5 0 9 0;
#X connect 6 0 7 0;
#X connect 8 0 11 0;
#X connect 9 0 8 0;
#X connect 9 0 1 0;
#X connect 10 0 11 0;
#X connect 12 0 8 0;
#X connect 12 0 1 0;
#X connect 12 0 9 0;
#X connect 13 0 10 0;
#X restore 112 118 pd dsp logic;
#X obj 315 2 inlet;
#X obj 80 110 bng 18 1000 50 0 THIS_IS_HERE_TO_GET_RID_OF_THE_OUTLET
\$0-MUTE_TOGGLE empty 0 9 2 8 -262144 -258699 -195568;
#X obj 191 2 inlet~;
#X obj 86 273 line~;
#X obj 186 333 *~;
#X obj 206 363 dac~;
#X text 203 22 audio in;
#X obj 254 2 inlet~;
#X obj 248 332 *~;
#X obj 201 73 hip~ 3;
#X obj 263 73 hip~ 3;
#X obj 12 288 send pd;
#X msg 12 267 dsp 1;
#X obj 248 362 outlet~;
#X obj 148 362 outlet~;
#X obj 355 362 outlet;
#X obj 86 252 pack 0 50;
#X text 153 251 <-- make a ramp to avoid clicks or zipper noise;
#X msg 86 217 0;
#X obj 86 194 moses 0.011;
#X text 307 74 filter out DC;
#N canvas 148 311 361 328 mute 0;
#X obj 23 20 inlet;
#X obj 173 20 inlet;
#X obj 222 208 float;
#X obj 265 121 tgl 15 1 empty empty empty 17 7 0 10 -262144 -1 -1 1
1;
#X obj 222 162 spigot;
#X obj 172 41 trigger bang bang;
#X obj 254 263 outlet;
#X msg 274 208 0;
#X obj 274 163 select 0;
#X obj 127 64 bang;
#X msg 127 85 set 1;
#X obj 65 304 send \$0-MUTE_TOGGLE;
#X msg 65 283 color \$1 13 20;
#X obj 65 235 bang;
#X msg 65 255 0;
#X msg 98 255 3;
#X connect 0 0 2 1;
#X connect 0 0 9 0;
#X connect 1 0 5 0;
#X connect 2 0 6 0;
#X connect 2 0 13 0;
#X connect 3 0 4 1;
#X connect 3 0 8 0;
#X connect 4 0 2 0;
#X connect 5 0 4 0;
#X connect 5 1 3 0;
#X connect 7 0 6 0;
#X connect 8 0 7 0;
#X connect 8 0 15 0;
#X connect 9 0 10 0;
#X connect 9 0 13 0;
#X connect 10 0 3 0;
#X connect 12 0 11 0;
#X connect 13 0 14 0;
#X connect 14 0 12 0;
#X connect 15 0 12 0;
#X restore 86 148 pd mute;
#X obj 315 25 t f f;
#X connect 0 0 15 0;
#X connect 0 0 18 0;
#X connect 0 0 22 0;
#X connect 0 0 24 0;
#X connect 1 0 2 0;
#X connect 3 0 25 0;
#X connect 4 0 24 1;
#X connect 5 0 12 0;
#X connect 6 0 11 0;
#X connect 6 0 7 0;
#X connect 7 0 8 0;
#X connect 7 0 17 0;
#X connect 10 0 13 0;
#X connect 11 0 8 1;
#X connect 11 0 16 0;
#X connect 12 0 7 1;
#X connect 13 0 11 1;
#X connect 15 0 14 0;
#X connect 19 0 6 0;
#X connect 21 0 19 0;
#X connect 22 0 21 0;
#X connect 22 1 19 0;
#X connect 24 0 22 0;
#X connect 24 0 0 0;
#X connect 25 0 24 0;
#X connect 25 0 18 0;
#X connect 25 0 22 0;
#X connect 25 0 15 0;
#X connect 25 1 0 0;
#X coords 0 0 1 1 90 40 1 10 90;
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment