Commit 9fc5a523 authored by Miller Puckette's avatar Miller Puckette
Browse files

updates to pd~

parent 5e3a3a8f
#N canvas 193 43 967 599 10;
#N canvas 304 143 967 599 10;
#X obj 370 524 spigot;
#X msg 442 397 bang;
#X obj 429 488 bonk~;
......@@ -131,6 +131,8 @@ set" in memory.;
the high one and then fall to the low one to make an attack. The unit
is the sum of the proportional growth in the 11 filter bands. Proportional
growth is essentially the logarithmic time derivative.;
#X text 238 27 (NOTE: this documentation does not yet describe new
features for Pd 0.42).;
#X connect 0 0 40 0;
#X connect 1 0 2 0;
#X connect 2 0 8 0;
......
/* Copyright 1997-1999 Miller Puckette (msp@ucsd.edu) and Ted Apel
(tapel@ucsd.edu). Permission is granted to use this software for any
noncommercial purpose. For commercial licensing please contact the UCSD
Technology Transfer Office.
THE AUTHORS AND THEIR EMPLOYERS MAKE NO WARRANTY, EXPRESS OR IMPLIED,
IN CONNECTION WITH THIS SOFTWARE!
/*
###########################################################################
# bonk~ - a Max/MSP external
# by miller puckette and ted appel
# http://crca.ucsd.edu/~msp/
# Max/MSP port by barry threw
# http://www.barrythrew.com
# me@barrythrew.com
# San Francisco, CA
# (c) 2008
# for Kesumo - http://www.kesumo.com
###########################################################################
// bonk~ detects attacks in an audio signal
###########################################################################
This software is copyrighted by Miller Puckette and others. The following
terms (the "Standard Improved BSD License") apply to all files associated with
the software unless explicitly disclaimed in individual files:
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
1. Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
2. 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.
3. The name of the author may not be used to endorse or promote
products derived from this software without specific prior
written permission.
THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``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 AUTHOR
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.
*/
/*
dolist:
decay and other times in msec
*/
#include <math.h>
#include <stdio.h>
#include <string.h>
#ifdef NT
#pragma warning (disable: 4305 4244 4996)
#pragma warning (disable: 4305 4244)
#endif
#ifdef MSP
#include "ext.h"
#include "z_dsp.h"
#include "math.h"
#include "ext_support.h"
#include "ext_proto.h"
#include "ext_obex.h"
typedef double t_floatarg; /* from m_pd.h */
#define flog log
......@@ -28,18 +71,6 @@ typedef double t_floatarg; /* from m_pd.h */
#define fsqrt sqrt
#define t_resizebytes(a, b, c) t_resizebytes((char *)(a), (b), (c))
#define flog log
#define fexp exp
#define fsqrt sqrt
#define FILE_DIALOG 1 /* use dialogs to get file name */
#define FILE_NAMED 2 /* symbol specifies file name */
#define DUMTAB1SIZE 256
#define DUMTAB2SIZE 1024
static float rsqrt_exptab[DUMTAB1SIZE], rsqrt_mantissatab[DUMTAB2SIZE];
void *bonk_class;
#define getbytes t_getbytes
#define freebytes t_freebytes
......@@ -50,28 +81,52 @@ void *bonk_class;
static t_class *bonk_class;
#endif
#ifndef _MSC_VER
#include <alloca.h>
#endif
/* ------------------------ bonk~ ----------------------------- */
#define NPOINTS 256
#define DEFNPOINTS 256
#define MAXCHANNELS 8
#define MINPOINTS 64
#define DEFPERIOD 128
#define DEFHITHRESH 60
#define DEFLOTHRESH 50
#define DEFNFILTERS 11
#define DEFHALFTONES 6
#define DEFOVERLAP 1
#define DEFFIRSTBIN 1
#define DEFHITHRESH 5
#define DEFLOTHRESH 2.5
#define DEFMASKTIME 4
#define DEFMASKDECAY 0.7
#define DEFDEBOUNCEDECAY 0
#define DEFMINVEL 7
#define DEFATTACKBINS 1
#define MAXATTACKWAIT 4
typedef struct _filterkernel
{
int k_npoints;
float k_freq;
float k_normalize;
int k_filterpoints;
int k_hoppoints;
int k_skippoints;
int k_nhops;
float k_centerfreq; /* center frequency, bins */
float k_bandwidth; /* bandwidth, bins */
float *k_stuff;
} t_filterkernel;
typedef struct _filterbank
{
int b_nfilters; /* number of filters in bank */
int b_npoints; /* input vector size */
float b_halftones; /* filter bandwidth in halftones */
float b_overlap; /* overlap; default 1 for 1/2-power pts */
float b_firstbin; /* freq of first filter in bins, default 1 */
t_filterkernel *b_vec; /* filter kernels */
int b_refcount; /* number of bonk~ objects using this */
struct _filterbank *b_next; /* next in linked list */
} t_filterbank;
#if 0 /* this is the design for 1.0: */
static t_filterkernel bonk_filterkernels[] =
{{256, 2, .01562}, {256, 4, .01562}, {256, 6, .01562}, {180, 6, .02222},
......@@ -79,6 +134,7 @@ static t_filterkernel bonk_filterkernels[] =
{32, 6, .03227}, {22, 6, .03932}, {16, 6, .04489}};
#endif
#if 0
/* here's the 1.1 rev: */
static t_filterkernel bonk_filterkernels[] =
{{256, 1, .01562, 0}, {256, 3, .01562, 0}, {256, 5, .01562, 0},
......@@ -86,27 +142,39 @@ static t_filterkernel bonk_filterkernels[] =
{76, 6, .0236, 0}, {54, 6, .02634, 0}, {38, 6, .03047, 0},
{26, 6, .03667, 0}, {18, 6, .04458, 0}};
#define NFILTERS ((int)(sizeof(bonk_filterkernels)/ \
sizeof(bonk_filterkernels[0])))
#define NFILTERS \
((int)(sizeof(bonk_filterkernels) / sizeof(bonk_filterkernels[0])))
#endif
#if 0
/* and 1.2 */
#define NFILTERS 11
static t_filterkernel bonk_filterkernels[NFILTERS];
#endif
/* and 1.3 */
#define MAXNFILTERS 50
#define MASKHIST 8
static float bonk_hanningwindow[NPOINTS];
static t_filterbank *bonk_filterbanklist;
typedef struct _hist
{
float h_power;
float h_mask;
float h_before;
int h_countup;
float h_mask[MASKHIST];
} t_hist;
typedef struct template
{
float t_amp[NFILTERS];
float t_amp[MAXNFILTERS];
} t_template;
typedef struct _insig
{
t_hist g_hist[NFILTERS]; /* history for each filter */
t_hist g_hist[MAXNFILTERS]; /* history for each filter */
#ifdef PD
t_outlet *g_outlet; /* outlet for raw data */
#endif
......@@ -126,85 +194,235 @@ typedef struct _bonk
#endif /* PD */
#ifdef MSP
t_pxobject x_obj;
void *obex;
void *x_cookedout;
void *x_clock;
short x_vol;
#endif /* MSP */
/* parameters */
int x_nsig;
int x_npoints; /* number of points in input buffer */
int x_period; /* number of input samples between analyses */
int x_nfilters; /* number of filters requested */
float x_halftones; /* nominal halftones between filters */
float x_overlap;
float x_firstbin;
float x_hithresh; /* threshold for total growth to trigger */
float x_lothresh; /* threshold for total growth to re-arm */
float x_minvel; /* minimum velocity we output */
float x_maskdecay;
int x_masktime;
int x_useloudness; /* use loudness spectra instead of power */
float x_debouncedecay;
float x_debouncevel;
double x_learndebounce; /* debounce time (in "learn" mode only) */
int x_attackbins; /* number of bins to wait for attack */
t_hist x_hist[NFILTERS];
t_filterbank *x_filterbank;
t_hist x_hist[MAXNFILTERS];
t_template *x_template;
t_insig *x_insig;
int x_ninsig;
int x_ntemplate;
int x_infill;
int x_countdown;
int x_period;
int x_willattack;
int x_attacked;
int x_debug;
float x_hithresh;
float x_lothresh;
int x_masktime;
float x_maskdecay;
int x_learn;
double x_learndebounce; /* debounce time for "learn" mode */
int x_learncount; /* countup for "learn" mode */
float x_debouncedecay;
float x_minvel; /* minimum velocity we output */
float x_debouncevel;
int x_spew; /* if true, always generate output! */
int x_maskphase; /* phase, 0 to MASKHIST-1, for mask history */
float x_sr; /* current sample rate in Hz. */
} t_bonk;
#ifdef MSP
static void *bonk_new(int period, int bonk2);
void bonk_tick(t_bonk *x);
void bonk_doit(t_bonk *x);
static void *bonk_new(t_symbol *s, long ac, t_atom *av);
static void bonk_tick(t_bonk *x);
static void bonk_doit(t_bonk *x);
static t_int *bonk_perform(t_int *w);
void bonk_dsp(t_bonk *x, t_signal **sp);
static void bonk_dsp(t_bonk *x, t_signal **sp);
void bonk_assist(t_bonk *x, void *b, long m, long a, char *s);
void bonk_thresh(t_bonk *x, t_floatarg f1, t_floatarg f2);
void bonk_mask(t_bonk *x, t_floatarg f1, t_floatarg f2);
void bonk_debounce(t_bonk *x, t_floatarg f1);
static void bonk_print(t_bonk *x, t_floatarg f);
static void bonk_learn(t_bonk *x, t_floatarg f);
void bonk_bang(t_bonk *x);
void bonk_setupkernels(void);
void bonk_free(t_bonk *x);
static void bonk_free(t_bonk *x);
void bonk_setup(void);
void main();
float qrsqrt(float f);
static void bonk_thresh(t_bonk *x, t_floatarg f1, t_floatarg f2);
static void bonk_mask(t_bonk *x, t_floatarg f1, t_floatarg f2);
static void bonk_print(t_bonk *x, t_floatarg f);
static void bonk_bang(t_bonk *x);
static void bonk_write(t_bonk *x, t_symbol *s);
static void bonk_read(t_bonk *x, t_symbol *s);
void bonk_minvel_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_lothresh_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_hithresh_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_masktime_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_maskdecay_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_debouncedecay_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_debug_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_spew_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_useloudness_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_attackbins_set(t_bonk *x, void *attr, long ac, t_atom *av);
void bonk_learn_set(t_bonk *x, void *attr, long ac, t_atom *av);
float qrsqrt(float f);
double clock_getsystime();
double clock_gettimesince(double prevsystime);
static char *strcpy(char *s1, const char *s2);
static int ilog2(int n);
char *strcpy(char *s1, const char *s2);
#endif
static void bonk_tick(t_bonk *x);
static void bonk_donew(t_bonk *x, int period, int nsig)
#define HALFWIDTH 0.75 /* half peak bandwidth at half power point in bins */
static t_filterbank *bonk_newfilterbank(int npoints, int nfilters,
float halftones, float overlap, float firstbin)
{
int i, j;
float cf, bw, h, relspace;
t_filterbank *b = (t_filterbank *)getbytes(sizeof(*b));
b->b_npoints = npoints;
b->b_nfilters = nfilters;
b->b_halftones = halftones;
b->b_overlap = overlap;
b->b_firstbin = firstbin;
b->b_refcount = 0;
b->b_next = bonk_filterbanklist;
bonk_filterbanklist = b;
b->b_vec = (t_filterkernel *)getbytes(nfilters * sizeof(*b->b_vec));
h = exp((log(2.)/12.)*halftones); /* specced interval between filters */
relspace = (h - 1)/(h + 1); /* nominal spacing-per-f for fbank */
cf = firstbin;
bw = cf * relspace * overlap;
if (bw < HALFWIDTH)
bw = HALFWIDTH;
for (i = 0; i < nfilters; i++)
{
float *fp, newcf, newbw;
float normalizer = 0;
int filterpoints, skippoints, hoppoints, nhops;
filterpoints = 0.5 + npoints * HALFWIDTH/bw;
if (cf > npoints/2)
{
post("bonk~: only using %d filters (ran past Nyquist)", i+1);
break;
}
if (filterpoints < 4)
{
post("bonk~: only using %d filters (kernels got too short)", i+1);
break;
}
else if (filterpoints > npoints)
filterpoints = npoints;
hoppoints = 0.5 + 0.5 * npoints * HALFWIDTH/bw;
nhops = 1. + (npoints-filterpoints)/(float)hoppoints;
skippoints = 0.5 * (npoints-filterpoints - (nhops-1) * hoppoints);
b->b_vec[i].k_stuff =
(float *)getbytes(2 * sizeof(float) * filterpoints);
b->b_vec[i].k_filterpoints = filterpoints;
b->b_vec[i].k_nhops = nhops;
b->b_vec[i].k_hoppoints = hoppoints;
b->b_vec[i].k_skippoints = skippoints;
b->b_vec[i].k_centerfreq = cf;
b->b_vec[i].k_bandwidth = bw;
for (fp = b->b_vec[i].k_stuff, j = 0; j < filterpoints; j++, fp+= 2)
{
float phase = j * cf * (2*3.14159/ npoints);
float wphase = j * (2*3.14159 / filterpoints);
float window = sin(0.5*wphase);
fp[0] = window * cos(phase);
fp[1] = window * sin(phase);
normalizer += window;
}
normalizer = 1/(normalizer * nhops);
for (fp = b->b_vec[i].k_stuff, j = 0;
j < filterpoints; j++, fp+= 2)
fp[0] *= normalizer, fp[1] *= normalizer;
#if 0
post("i %d cf %.2f bw %.2f nhops %d, hop %d, skip %d, npoints %d",
i, cf, bw, nhops, hoppoints, skippoints, filterpoints);
#endif
newcf = (cf + bw/overlap)/(1 - relspace);
newbw = newcf * overlap * relspace;
if (newbw < HALFWIDTH)
{
newbw = HALFWIDTH;
newcf = cf + 2 * HALFWIDTH / overlap;
}
cf = newcf;
bw = newbw;
}
for (; i < nfilters; i++)
b->b_vec[i].k_stuff = 0, b->b_vec[i].k_filterpoints = 0;
return (b);
}
static void bonk_freefilterbank(t_filterbank *b)
{
t_filterbank *b2, *b3;
int i;
post("free filterbank");
if (bonk_filterbanklist == b)
bonk_filterbanklist = b->b_next;
else for (b2 = bonk_filterbanklist; b3 = b2->b_next; b2 = b3)
if (b3 == b)
{
b2->b_next = b3->b_next;
break;
}
for (i = 0; i < b->b_nfilters; i++)
if (b->b_vec[i].k_stuff)
freebytes(b->b_vec[i].k_stuff,
b->b_vec[i].k_filterpoints * sizeof(float));
freebytes(b, sizeof(*b));
post("done free");
}
static void bonk_donew(t_bonk *x, int npoints, int period, int nsig,
int nfilters, float halftones, float overlap, float firstbin,
float samplerate)
{
int i, j;
t_hist *h;
float *fp;
t_insig *g;
t_filterbank *fb;
for (j = 0, g = x->x_insig; j < nsig; j++, g++)
{
for (i = 0, h = g->g_hist; i--; h++)
h->h_power = h->h_mask = h->h_before = 0, h->h_countup = 0;
/* we ought to check for failure to allocate memory here */
g->g_inbuf = (float *)getbytes(NPOINTS * sizeof(float));
for (i = NPOINTS, fp = g->g_inbuf; i--; fp++) *fp = 0;
{
h->h_power = h->h_before = 0, h->h_countup = 0;
for (j = 0; j < MASKHIST; j++)
h->h_mask[j] = 0;
}
/* we ought to check for failure to allocate memory here */
g->g_inbuf = (float *)getbytes(npoints * sizeof(float));
for (i = npoints, fp = g->g_inbuf; i--; fp++) *fp = 0;
}
if (!period) period = npoints/2;
x->x_npoints = npoints;
x->x_period = period;
x->x_ninsig = nsig;
x->x_nfilters = nfilters;
x->x_halftones = halftones;
x->x_template = (t_template *)getbytes(0);
x->x_ntemplate = 0;
x->x_infill = 0;
x->x_countdown = 0;
if (!period) period = NPOINTS/2;
x->x_period = 1 << ilog2(period);
x->x_willattack = 0;
x->x_attacked = 0;
x->x_maskphase = 0;
x->x_debug = 0;
x->x_hithresh = DEFHITHRESH;
x->x_lothresh = DEFLOTHRESH;
......@@ -215,33 +433,48 @@ static void bonk_donew(t_bonk *x, int period, int nsig)
x->x_learncount = 0;
x->x_debouncedecay = DEFDEBOUNCEDECAY;
x->x_minvel = DEFMINVEL;
x->x_useloudness = 0;
x->x_debouncevel = 0;
x->x_attackbins = DEFATTACKBINS;
x->x_sr = samplerate;
for (fb = bonk_filterbanklist; fb; fb = fb->b_next)
if (fb->b_nfilters == x->x_nfilters &&
fb->b_halftones == x->x_halftones &&
fb->b_firstbin == firstbin &&
fb->b_overlap == overlap &&
fb->b_npoints == x->x_npoints)
fb->b_refcount++, x->x_filterbank = fb;
if (!x->x_filterbank)
x->x_filterbank = bonk_newfilterbank(npoints, nfilters,
halftones, overlap, firstbin),
x->x_filterbank->b_refcount++;
}
static void bonk_print(t_bonk *x, t_floatarg f);
static void bonk_dotick(t_bonk *x, int hit)
{
t_atom at[NFILTERS], *ap, at2[3];
int i, j, k, n;
t_atom at[MAXNFILTERS], *ap, at2[3];
int i, j, k, n, maskphase = x->x_maskphase;
t_hist *h;
float powerout[NFILTERS*MAXCHANNELS], *pp, vel = 0, temperature = 0;
float *pp, vel = 0, temperature = 0;
float *fp;
t_template *tp;
int nfit, ninsig = x->x_ninsig, ntemplate = x->x_ntemplate;
int nfit, ninsig = x->x_ninsig, ntemplate = x->x_ntemplate,
nfilters = x->x_nfilters;
t_insig *gp;
int totalbins = NFILTERS * ninsig;
#ifdef _MSC_VER
float powerout[MAXNFILTERS*MAXCHANNELS];
#else
float *powerout = alloca(x->x_nfilters * x->x_ninsig * sizeof(*powerout));
#endif
x->x_willattack = 0;
for (i = ninsig, pp = powerout, gp = x->x_insig; i--; gp++)
{
for (j = 0, h = gp->g_hist; j < NFILTERS; j++, h++, pp++)
for (j = 0, h = gp->g_hist; j < nfilters; j++, h++, pp++)
{
float power = (hit ? h->h_mask - h->h_before : h->h_power);
float power =
(hit ? h->h_mask[maskphase] - h->h_before : h->h_power);
float intensity = *pp =
(power > 0 ? 100. * qrsqrt(qrsqrt(power)) : 0);
(power > 0 ? 100. * qrsqrt(qrsqrt(power)) : 0);
vel += intensity;
temperature += intensity * (float)j;
}
......@@ -251,21 +484,21 @@ static void bonk_dotick(t_bonk *x, int hit)
vel *= 0.5 / ninsig; /* fudge factor */
if (hit)
{
/* if hit nonzero it's a clock callback. if in "learn" mode update the
template list; in any event match the hit to known templates. */
/* if hit nonzero it's a clock callback. if in "learn" mode update the
template list; in any event match the hit to known templates. */
if (vel < x->x_debouncevel)
{
if (x->x_debug)
post("bounce cancelled: vel %f debounce %f",
vel, x->x_debouncevel);
vel, x->x_debouncevel);
return;
}
if (vel < x->x_minvel)
{
if (x->x_debug)
post("low velocity cancelled: vel %f, minvel %f",
vel, x->x_minvel);
vel, x->x_minvel);
return;
}
x->x_debouncevel = vel;
......@@ -276,23 +509,23 @@ static void bonk_dotick(t_bonk *x, int hit)
if ((!ntemplate) || (msec > 200))
{
int countup = x->x_learncount;
/* normalize to 100 */
/* normalize to 100 */
float norm;
for (i = NFILTERS * ninsig, norm = 0, pp = powerout; i--; pp++)
for (i = nfilters * ninsig, norm = 0, pp = powerout; i--; pp++)
norm += *pp * *pp;
if (norm < 1.0e-15) norm = 1.0e-15;
norm = 100.f * qrsqrt(norm);
/* check if this is the first strike for a new template */
/* check if this is the first strike for a new template */
if (!countup)
{
int oldn = ntemplate;
x->x_ntemplate = ntemplate = oldn + ninsig;
x->x_template = (t_template *)t_resizebytes(x->x_template,
oldn * sizeof(x->x_template[0]),
ntemplate * sizeof(x->x_template[0]));
ntemplate * sizeof(x->x_template[0]));
for (i = ninsig, pp = powerout; i--; oldn++)
for (j = NFILTERS, fp = x->x_template[oldn].t_amp; j--;
pp++, fp++)
for (j = nfilters, fp = x->x_template[oldn].t_amp; j--;
pp++, fp++)
*fp = *pp * norm;
}
else
......@@ -301,10 +534,10 @@ static void bonk_dotick(t_bonk *x, int hit)
if (oldn < 0) post("bonk_tick bug");
for (i = ninsig, pp = powerout; i--; oldn++)