From 1d427b8655e26e99906ac253f8e4a0bde468093f Mon Sep 17 00:00:00 2001 From: carmen rocco Date: Thu, 16 Sep 2004 18:01:09 +0000 Subject: dropped in goodie bag svn path=/trunk/; revision=2041 --- externals/signal/fiddle~/fiddle~.c | 1854 ++++++++++++++++++++++++++++++ externals/signal/fiddle~/help-fiddle~.pd | 142 +++ 2 files changed, 1996 insertions(+) create mode 100644 externals/signal/fiddle~/fiddle~.c create mode 100644 externals/signal/fiddle~/help-fiddle~.pd (limited to 'externals/signal/fiddle~') diff --git a/externals/signal/fiddle~/fiddle~.c b/externals/signal/fiddle~/fiddle~.c new file mode 100644 index 00000000..6ead7671 --- /dev/null +++ b/externals/signal/fiddle~/fiddle~.c @@ -0,0 +1,1854 @@ +/* Copyright (c) 1997-1999 Miller Puckette and Ted Apel. +* For information on usage and redistribution, and for a DISCLAIMER OF ALL +* WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ + +/* + * Fiddle is a pitch tracker hardwired to have hop size ("H") equal to + * half its window size ("N"). + * + * This version should compile for Max "0.26," JMAX, Pd, or Max/MSP. + * + * The "lastanalysis" field holds the shifted FT of the previous H + * samples. The buffer contains in effect points 1/2, 3/2, ..., (N-1)/2 + * of the DTFT of a real vector of length N, half of whose points are zero, + * i.e., only the first H points are used. Put another way, we get the + * the odd-numbered points of the FFT of the H points, zero padded to 4*H in + * length. The integer points 0, 1, ..., H-1 + * are found by interpolating these others, using the fact that the + * half-integer points are band-limited (they only have positive frequencies.) + * To facilitate the interpolation the "lastanalysis" buffer contains + * FILTSIZE extra points (1/2-FILTSIZE, ..., -1/2) at the beginning and + * FILTSIZE again at the end ((N+1)/2, ..., FILTSIZE+(N-1)/2). The buffer + * therefore has N+4*FILTSIZE floating-point numbers in it. + * + * after doing this I found out that you can just do a real FFT + * of the H new points, zero-padded to contain N points, and using a similar + * but simpler interpolation scheme you can still get 2N points of the DTFT + * of the N points. Jean Laroche is a big fat hen. + * + */ + +#ifdef NT +#define flog log +#define fexp exp +#define fsqrt sqrt +#pragma warning (disable: 4305 4244) +#else +#define flog log +#define fexp exp +#define fsqrt sqrt +#endif + +char fiddle_version[] = "fiddle version 1.1 TEST4"; + +#ifdef JMAX +#include "fts.h" +#include +#include +typedef float t_float; +typedef float t_floatarg; +typedef fts_symbol_t t_symbol; + +static void *getbytes(size_t nbytes) +{ + void *ret; + if (nbytes < 1) nbytes = 1; + ret = (void *)malloc(nbytes); + return (ret); +} + +static void *resizebytes(void *old, size_t oldsize, size_t newsize) +{ + void *ret; + if (newsize < 1) newsize = 1; + ret = (void *)realloc((char *)old, newsize); + return (ret); +} + +static void freebytes(void *fatso, size_t nbytes) +{ + free(fatso); +} + +#define CLASSNAME "fiddle" + +#define OUTLETpower 5 +#define OUTLETmicropitch1 4 +#define OUTLETmicropitch2 3 +#define OUTLETmicropitch3 2 +#define OUTLETattack 1 +#define OUTLETpitch 0 + +static fts_symbol_t *dsp_symbol = 0; +#define error post + +#endif /* FTS */ + +#ifdef MAX26 +#define t_floatarg double +#include "m_extern.h" +#include "d_graph.h" +#include "d_ugen.h" +#endif /* MAX26 */ + +#ifdef PD +#include "m_pd.h" +#endif /* PD */ + +#ifdef MSP +#define flog log +#define fexp exp +#define fsqrt sqrt +#endif /* MSP */ + +#ifdef MSP +#define t_floatarg double // this is a guess based on MAX26 +#include "ext.h" +#include "z_dsp.h" +#include "fft_mayer.proto.h" +//#include "fiddle_header.h" +// #include "MacHeaders.h" +//#include + +//#include "fiddledoit.h" + +#endif /* MSP */ + +#include + + +#define MINBIN 3 +#define DEFAMPLO 40 +#define DEFAMPHI 50 +#define DEFATTACKTIME 100 +#define DEFATTACKTHRESH 10 +#define DEFVIBTIME 50 +#define DEFVIBDEPTH 0.5 +#define GLISS 0.7f +#define DBFUDGE 30.8f +#define MINFREQINBINS 5 /* minimum frequency in bins for reliable output */ + +#define MAXNPITCH 3 +#define MAXHIST 3 /* find N hottest peaks in histogram */ + +#define MAXPOINTS 8192 +#define MINPOINTS 128 +#define DEFAULTPOINTS 1024 + +#define HISTORY 20 +#define MAXPEAK 100 /* maximum number of peaks */ +#define DEFNPEAK 20 /* default number of peaks */ + +#define MAXNPEAK (MAXLOWPEAK + MAXSTRONGPEAK) +#define MINBW (0.03f) /* consider BW >= 0.03 FFT bins */ + +#define BINPEROCT 48 /* bins per octave */ +#define BPERO_OVER_LOG2 69.24936196f /* BINSPEROCT/log(2) */ +#define FACTORTOBINS (float)(4/0.0145453) /* 4 / (pow(2.,1/48.) - 1) */ +#define BINGUARD 10 /* extra bins to throw in front */ +#define PARTIALDEVIANCE 0.023f /* acceptable partial detuning in % */ +#define LOGTODB 4.34294481903f /* 20/log(10) */ + +#define KNOCKTHRESH 10.f /* don't know how to describe this */ + + +static float sigfiddle_partialonset[] = +{ +0, +48, +76.0782000346154967102, +96, +111.45254855459339269887, +124.07820003461549671089, +134.75303625876499715823, +144, +152.15640006923099342109, +159.45254855459339269887, +166.05271769459026829915, +172.07820003461549671088, +177.62110647077242370064, +182.75303625876499715892, +187.53074858920888940907, +192, +}; + +#define NPARTIALONSET ((int)(sizeof(sigfiddle_partialonset)/sizeof(float))) + +static int sigfiddle_intpartialonset[] = +{ +0, +48, +76, +96, +111, +124, +135, +144, +152, +159, +166, +172, +178, +183, +188, +192, +}; + +/* these coefficients, which come from the "upsamp" subdirectory, +are a filter kernel for upsampling by a factor of two, assuming +the sound to be upsampled has no energy above half the Nyquist, i.e., +that it's already 2x oversampled compared to the theoretically possible +sample rate. I got these by trial and error. */ + +#define FILT1 ((float)(.5 * 1.227054)) +#define FILT2 ((float)(.5 * -0.302385)) +#define FILT3 ((float)(.5 * 0.095326)) +#define FILT4 ((float)(.5 * -0.022748)) +#define FILT5 ((float)(.5 * 0.002533)) +#define FILTSIZE 5 + +typedef struct peakout /* a peak for output */ +{ + float po_freq; /* frequency in hz */ + float po_amp; /* amplitude */ +} t_peakout; + +typedef struct peak /* a peak for analysis */ +{ + float p_freq; /* frequency in bins */ + float p_width; /* peak width in bins */ + float p_pow; /* peak power */ + float p_loudness; /* 4th root of power */ + float *p_fp; /* pointer back to spectrum */ +} t_peak; + +typedef struct histopeak +{ + float h_pitch; /* estimated pitch */ + float h_value; /* value of peak */ + float h_loud; /* combined strength of found partials */ + int h_index; /* index of bin holding peak */ + int h_used; /* true if an x_hist entry points here */ +} t_histopeak; + +typedef struct pitchhist /* struct for keeping history by pitch */ +{ + float h_pitch; /* pitch to output */ + float h_amps[HISTORY]; /* past amplitudes */ + float h_pitches[HISTORY]; /* past pitches */ + float h_noted; /* last pitch output */ + int h_age; /* number of frames pitch has been there */ + t_histopeak *h_wherefrom; /* new histogram peak to incorporate */ + void *h_outlet; +} t_pitchhist; + +typedef struct sigfiddle /* instance struct */ +{ +#ifdef JMAX + fts_object_t x_h; /* object header */ + fts_alarm_t x_clock; /* callback for timeouts */ +#endif +#ifdef MAX26 + t_head x_h; /* header for tilde objects */ + t_sig *x_io[IN1+OUT0]; /* number of signal inputs and outputs */ + void *x_clock; /* a "clock" object */ +#endif +#ifdef PD + t_object x_ob; /* object header */ + t_clock *x_clock; /* callback for timeouts */ +#endif +#ifdef MSP + t_pxobject x_obj; + void *x_clock; + long x_downsample; // downsample feature because of + // MSP's large sig vector sizes +#endif + float *x_inbuf; /* buffer to analyze, npoints/2 elems */ + float *x_lastanalysis; /* FT of last buffer (see main comment) */ + float *x_spiral; /* 1/4-wave complex exponential */ + t_peakout *x_peakbuf; /* spectral peaks for output */ + int x_npeakout; /* number of spectral peaks to output */ + int x_npeakanal; /* number of spectral peaks to analyze */ + int x_phase; /* number of points since last output */ + int x_histphase; /* phase into amplitude history vector */ + int x_hop; /* period of output, npoints/2 */ + float x_sr; /* sample rate */ + t_pitchhist x_hist[MAXNPITCH]; /* history of current pitches */ + int x_nprint; /* how many periods to print */ + int x_npitch; /* number of simultaneous pitches */ + float x_dbs[HISTORY]; /* DB history, indexed by "histphase" */ + float x_peaked; /* peak since last attack */ + int x_dbage; /* number of bins DB has met threshold */ + int x_auto; /* true if generating continuous output */ +/* parameters */ + float x_amplo; + float x_amphi; + int x_attacktime; + int x_attackbins; + float x_attackthresh; + int x_vibtime; + int x_vibbins; + float x_vibdepth; + float x_npartial; +/* outlets & clock */ + void *x_envout; + int x_attackvalue; + void *x_attackout; + void *x_noteout; + void *x_peakout; +} t_sigfiddle; + +#if CHECKER +float fiddle_checker[1024]; +#endif + +#ifdef MSP +// Mac compiler requires prototypes for everything + +int sigfiddle_ilog2(int n); +float fiddle_mtof(float f); +float fiddle_ftom(float f); +void sigfiddle_doit(t_sigfiddle *x); +void sigfiddle_debug(t_sigfiddle *x); +void sigfiddle_print(t_sigfiddle *x); +void sigfiddle_assist(t_sigfiddle *x, void *b, long m, long a, char *s); +void sigfiddle_amprange(t_sigfiddle *x, double amplo, double amphi); +void sigfiddle_reattack(t_sigfiddle *x, t_floatarg attacktime, t_floatarg +attackthresh); +void sigfiddle_vibrato(t_sigfiddle *x, t_floatarg vibtime, t_floatarg +vibdepth); +void sigfiddle_npartial(t_sigfiddle *x, double npartial); +void sigfiddle_auto(t_sigfiddle *x, t_floatarg f); +void sigfiddle_setnpoints(t_sigfiddle *x, t_floatarg f); +int sigfiddle_doinit(t_sigfiddle *x, long npoints, long npitch, long +npeakanal, long npeakout); +static t_int *fiddle_perform(t_int *w); +void sigfiddle_dsp(t_sigfiddle *x, t_signal **sp); +void sigfiddle_tick(t_sigfiddle *x); +void sigfiddle_bang(t_sigfiddle *x); +void sigfiddle_ff(t_sigfiddle *x); +//void *sigfiddle_new(long npoints, long npitch); +void *sigfiddle_new(long npoints, long npitch, + long npeakanal, long npeakout); +void msp_fft(float *buf, long np, long inv); +float msp_ffttemp[MAXPOINTS*2]; +int errno; +#endif + +int sigfiddle_ilog2(int n) +{ + int ret = -1; + while (n) + { + n >>= 1; + ret++; + } + return (ret); +} + +float fiddle_mtof(float f) +{ + return (8.17579891564 * exp(.0577622650 * f)); +} + +float fiddle_ftom(float f) +{ + return (17.3123405046 * log(.12231220585 * f)); +} +#define ftom fiddle_ftom +#define mtof fiddle_mtof + +void sigfiddle_doit(t_sigfiddle *x) +{ +#ifdef MSP + // prevents interrupt-level stack overflow crash with Netscape. + static float spect1[4*MAXPOINTS]; + static float spect2[MAXPOINTS + 4*FILTSIZE]; +#else + float spect1[4*MAXPOINTS]; + float spect2[MAXPOINTS + 4*FILTSIZE]; +#endif +#if CHECKER + float checker3[4*MAXPOINTS]; +#endif + + t_peak peaklist[MAXPEAK + 1], *pk1; + t_peakout *pk2; + t_histopeak histvec[MAXHIST], *hp1; + int i, j, k, hop = x->x_hop, n = 2*hop, npeak, npitch, + logn = sigfiddle_ilog2(n), newphase, oldphase; + float *fp, *fp1, *fp2, *fp3, total_power, total_loudness, total_db; + float maxbin = BINPEROCT * (logn-2), *histogram = spect2 + BINGUARD; + t_pitchhist *phist; + float hzperbin = x->x_sr / (2.0f * n); + int npeakout = x->x_npeakout, npeakanal = x->x_npeakanal; + int npeaktot = (npeakout > npeakanal ? npeakout : npeakanal); + + oldphase = x->x_histphase; + newphase = x->x_histphase + 1; + if (newphase == HISTORY) newphase = 0; + x->x_histphase = newphase; + + /* + * multiply the H points by a 1/4-wave complex exponential, + * and take FFT of the result. + */ + for (i = 0, fp1 = x->x_inbuf, fp2 = x->x_spiral, fp3 = spect1; + i < hop; i++, fp1++, fp2 += 2, fp3 += 2) + fp3[0] = fp1[0] * fp2[0], fp3[1] = fp1[0] * fp2[1]; + +#ifdef MAX26 + fft(spect1, hop, 0); +#endif +#ifdef PD + pd_fft(spect1, hop, 0); +#endif +#ifdef JMAX + fts_cfft_inplc((complex *)spect1, hop); +#endif +#ifdef MSP + msp_fft(spect1,hop,0); +#endif + /* + * now redistribute the points to get in effect the odd-numbered + * points of the FFT of the H points, zero padded to 4*H in length. + */ + for (i = 0, fp1 = spect1, fp2 = spect2 + (2*FILTSIZE); + i < (hop>>1); i++, fp1 += 2, fp2 += 4) + fp2[0] = fp1[0], fp2[1] = fp1[1]; + for (i = 0, fp1 = spect1 + n - 2, fp2 = spect2 + (2*FILTSIZE+2); + i < (hop>>1); i++, fp1 -= 2, fp2 += 4) + fp2[0] = fp1[0], fp2[1] = -fp1[1]; + for (i = 0, fp1 = spect2 + (2*FILTSIZE), fp2 = spect2 + (2*FILTSIZE-2); + ix_lastanalysis + 2*FILTSIZE, + fp3 = spect2 + 2*FILTSIZE; + i < (hop>>1); i++) + { + float re, im; + + re= FILT1 * ( fp2[ -2] -fp2[ 1] +fp3[ -2] -fp3[ 1]) + + FILT2 * ( fp2[ -3] -fp2[ 2] +fp3[ -3] -fp3[ 2]) + + FILT3 * (-fp2[ -6] +fp2[ 5] -fp3[ -6] +fp3[ 5]) + + FILT4 * (-fp2[ -7] +fp2[ 6] -fp3[ -7] +fp3[ 6]) + + FILT5 * ( fp2[-10] -fp2[ 9] +fp3[-10] -fp3[ 9]); + + im= FILT1 * ( fp2[ -1] +fp2[ 0] +fp3[ -1] +fp3[ 0]) + + FILT2 * (-fp2[ -4] -fp2[ 3] -fp3[ -4] -fp3[ 3]) + + FILT3 * (-fp2[ -5] -fp2[ 4] -fp3[ -5] -fp3[ 4]) + + FILT4 * ( fp2[ -8] +fp2[ 7] +fp3[ -8] +fp3[ 7]) + + FILT5 * ( fp2[ -9] +fp2[ 8] +fp3[ -9] +fp3[ 8]); + + fp1[0] = 0.7071f * (re + im); + fp1[1] = 0.7071f * (im - re); + fp1[4] = fp2[0] + fp3[1]; + fp1[5] = fp2[1] - fp3[0]; + + fp1 += 8, fp2 += 2, fp3 += 2; + re= FILT1 * ( fp2[ -2] -fp2[ 1] -fp3[ -2] +fp3[ 1]) + + FILT2 * ( fp2[ -3] -fp2[ 2] -fp3[ -3] +fp3[ 2]) + + FILT3 * (-fp2[ -6] +fp2[ 5] +fp3[ -6] -fp3[ 5]) + + FILT4 * (-fp2[ -7] +fp2[ 6] +fp3[ -7] -fp3[ 6]) + + FILT5 * ( fp2[-10] -fp2[ 9] -fp3[-10] +fp3[ 9]); + + im= FILT1 * ( fp2[ -1] +fp2[ 0] -fp3[ -1] -fp3[ 0]) + + FILT2 * (-fp2[ -4] -fp2[ 3] +fp3[ -4] +fp3[ 3]) + + FILT3 * (-fp2[ -5] -fp2[ 4] +fp3[ -5] +fp3[ 4]) + + FILT4 * ( fp2[ -8] +fp2[ 7] -fp3[ -8] -fp3[ 7]) + + FILT5 * ( fp2[ -9] +fp2[ 8] -fp3[ -9] -fp3[ 8]); + + fp1[0] = 0.7071f * (re + im); + fp1[1] = 0.7071f * (im - re); + fp1[4] = fp2[0] - fp3[1]; + fp1[5] = fp2[1] + fp3[0]; + + fp1 += 8, fp2 += 2, fp3 += 2; + } +#if 0 + if (x->x_nprint) + { + for (i = 0, fp = spect1; i < 16; i++, fp+= 4) + post("spect %d %f %f --> %f", i, fp[0], fp[1], + sqrt(fp[0] * fp[0] + fp[1] * fp[1])); + } +#endif + /* copy new spectrum out */ + for (i = 0, fp1 = spect2, fp2 = x->x_lastanalysis; + i < n + 4*FILTSIZE; i++) *fp2++ = *fp1++; + + for (i = 0; i < MINBIN; i++) spect1[4*i + 2] = spect1[4*i + 3] = 0; + /* starting at bin MINBIN, compute hanning windowed power spectrum */ + for (i = MINBIN, fp1 = spect1+4*MINBIN, total_power = 0; + i < n-2; i++, fp1 += 4) + { + float re = fp1[0] - 0.5f * (fp1[-8] + fp1[8]); + float im = fp1[1] - 0.5f * (fp1[-7] + fp1[9]); + fp1[3] = (total_power += (fp1[2] = re * re + im * im)); + } + + if (total_power > 1e-9f) + { + total_db = (100.f - DBFUDGE) + LOGTODB * log(total_power/n); + total_loudness = fsqrt(fsqrt(total_power)); + if (total_db < 0) total_db = 0; + } + else total_db = total_loudness = 0; + /* store new db in history vector */ + x->x_dbs[newphase] = total_db; + if (total_db < x->x_amplo) goto nopow; +#if 1 + if (x->x_nprint) post("power %f", total_power); +#endif + +#if CHECKER + /* verify that our FFT resampling thing is putting out good results */ + for (i = 0; i < hop; i++) + { + checker3[2*i] = fiddle_checker[i]; + checker3[2*i + 1] = 0; + checker3[n + 2*i] = fiddle_checker[i] = x->x_inbuf[i]; + checker3[n + 2*i + 1] = 0; + } + for (i = 2*n; i < 4*n; i++) checker3[i] = 0; + fft(checker3, 2*n, 0); + if (x->x_nprint) + { + for (i = 0, fp = checker3; i < 16; i++, fp += 2) + post("spect %d %f %f --> %f", i, fp[0], fp[1], + sqrt(fp[0] * fp[0] + fp[1] * fp[1])); + } + +#endif + npeak = 0; + + /* search for peaks */ + for (i = MINBIN, fp = spect1+4*MINBIN, pk1 = peaklist; + i < n-2 && npeak < npeaktot; i++, fp += 4) + { + float height = fp[2], h1 = fp[-2], h2 = fp[6]; + float totalfreq, pfreq, f1, f2, m, var, stdev; + + if (height < h1 || height < h2 || + h1 < 0.00001f*total_power || h2 < 0.00001f*total_power) + continue; + + /* use an informal phase vocoder to estimate the frequency. + Do this for the two adjacent bins too. */ + pfreq= ((fp[-8] - fp[8]) * (2.0f * fp[0] - fp[8] - fp[-8]) + + (fp[-7] - fp[9]) * (2.0f * fp[1] - fp[9] - fp[-7])) / + (2.0f * height); + f1= ((fp[-12] - fp[4]) * (2.0f * fp[-4] - fp[4] - fp[-12]) + + (fp[-11] - fp[5]) * (2.0f * fp[-3] - fp[5] - fp[-11])) / + (2.0f * h1) - 1; + f2= ((fp[-4] - fp[12]) * (2.0f * fp[4] - fp[12] - fp[-4]) + + (fp[-3] - fp[13]) * (2.0f * fp[5] - fp[13] - fp[-3])) / + (2.0f * h2) + 1; + + /* get sample mean and variance of the three */ + m = 0.333333f * (pfreq + f1 + f2); + var = 0.5f * ((pfreq-m)*(pfreq-m) + (f1-m)*(f1-m) + (f2-m)*(f2-m)); + + totalfreq = i + m; + if (var * total_power > KNOCKTHRESH * height || var < 1e-30) + { +#if 0 + if (x->x_nprint) + post("cancel: %.2f hz, index %.1f, power %.5f, stdev=%.2f", + totalfreq * hzperbin, BPERO_OVER_LOG2 * log(totalfreq) - 96, + height, sqrt(var)); +#endif + continue; + } + stdev = fsqrt(var); + if (totalfreq < 4) + { + if (x->x_nprint) post("oops: was %d, freq %f, m %f, stdev %f h %f", + i, totalfreq, m, stdev, height); + totalfreq = 4; + } + pk1->p_width = stdev; + + pk1->p_pow = height; + pk1->p_loudness = fsqrt(fsqrt(height)); + pk1->p_fp = fp; + pk1->p_freq = totalfreq; + npeak++; +#if 1 + if (x->x_nprint) + { + post("peak: %.2f hz. index %.1f, power %.5f, stdev=%.2f", + pk1->p_freq * hzperbin, + BPERO_OVER_LOG2 * log(pk1->p_freq) - 96, + height, stdev); + } +#endif + pk1++; + } + + /* prepare the raw peaks for output */ + for (i = 0, pk1 = peaklist, pk2 = x->x_peakbuf; i < npeak; + i++, pk1++, pk2++) + { + float loudness = pk1->p_loudness; + if (i >= npeakout) break; + pk2->po_freq = hzperbin * pk1->p_freq; + pk2->po_amp = (2.f / (float)n) * (loudness * loudness); + } + for (; i < npeakout; i++, pk2++) pk2->po_amp = pk2->po_freq = 0; + + /* now, working back into spect2, make a sort of "liklihood" + * spectrum. Proceeding in 48ths of an octave, from 2 to + * n/2 (in bins), the likelihood of each pitch range is contributed + * to by every peak in peaklist that's an integer multiple of it + * in frequency. + */ + + if (npeak > npeakanal) npeak = npeakanal; /* max # peaks to analyze */ + for (i = 0, fp1 = histogram; i < maxbin; i++) *fp1++ = 0; + for (i = 0, pk1 = peaklist; i < npeak; i++, pk1++) + { + float pit = BPERO_OVER_LOG2 * flog(pk1->p_freq) - 96.0f; + float binbandwidth = FACTORTOBINS * pk1->p_width/pk1->p_freq; + float putbandwidth = (binbandwidth < 2 ? 2 : binbandwidth); + float weightbandwidth = (binbandwidth < 1.0f ? 1.0f : binbandwidth); + /* float weightamp = 1.0f + 3.0f * pk1->p_pow / pow; */ + float weightamp = 4. * pk1->p_loudness / total_loudness; + for (j = 0, fp2 = sigfiddle_partialonset; j < NPARTIALONSET; j++, fp2++) + { + float bin = pit - *fp2; + if (bin < maxbin) + { + float para, pphase, score = 30.0f * weightamp / + ((j+x->x_npartial) * weightbandwidth); + int firstbin = bin + 0.5f - 0.5f * putbandwidth; + int lastbin = bin + 0.5f + 0.5f * putbandwidth; + int ibw = lastbin - firstbin; + if (firstbin < -BINGUARD) break; + para = 1.0f / (putbandwidth * putbandwidth); + for (k = 0, fp3 = histogram + firstbin, + pphase = firstbin-bin; k <= ibw; + k++, fp3++, pphase += 1.0f) + { + *fp3 += score * (1.0f - para * pphase * pphase); + } + } + } + } +#if 1 + if (x->x_nprint) + { + for (i = 0; i < 6*5; i++) + { + float fhz = hzperbin * exp ((8*i + 96) * (1./BPERO_OVER_LOG2)); + if (!(i % 6)) post("-- bin %d pitch %f freq %f----", 8*i, + ftom(fhz), fhz);; + post("%3d %3d %3d %3d %3d %3d %3d %3d", + (int)(histogram[8*i]), + (int)(histogram[8*i+1]), + (int)(histogram[8*i+2]), + (int)(histogram[8*i+3]), + (int)(histogram[8*i+4]), + (int)(histogram[8*i+5]), + (int)(histogram[8*i+6]), + (int)(histogram[8*i+7])); + } + } + +#endif + + /* + * Next we find up to NPITCH strongest peaks in the histogram. + * if a peak is related to a stronger one via an interval in + * the sigfiddle_partialonset array, we suppress it. + */ + + for (npitch = 0; npitch < x->x_npitch; npitch++) + { + int indx; + float best; + if (npitch) + { + for (best = 0, indx = -1, j=1; j < maxbin-1; j++) + { + if (histogram[j] > best && histogram[j] > histogram[j-1] && + histogram[j] > histogram[j+1]) + { + for (k = 0; k < npitch; k++) + if (histvec[k].h_index == j) + goto peaknogood; + for (k = 0; k < NPARTIALONSET; k++) + { + if (j - sigfiddle_intpartialonset[k] < 0) break; + if (histogram[j - sigfiddle_intpartialonset[k]] + > histogram[j]) goto peaknogood; + } + for (k = 0; k < NPARTIALONSET; k++) + { + if (j + sigfiddle_intpartialonset[k] >= maxbin) break; + if (histogram[j + sigfiddle_intpartialonset[k]] + > histogram[j]) goto peaknogood; + } + indx = j; + best = histogram[j]; + } + peaknogood: ; + } + } + else + { + for (best = 0, indx = -1, j=0; j < maxbin; j++) + if (histogram[j] > best) + indx = j, best = histogram[j]; + } + if (indx < 0) break; + histvec[npitch].h_value = best; + histvec[npitch].h_index = indx; + } +#if 1 + if (x->x_nprint) + { + for (i = 0; i < npitch; i++) + { + post("index %d freq %f --> value %f", histvec[i].h_index, + exp((1./BPERO_OVER_LOG2) * (histvec[i].h_index + 96)), + histvec[i].h_value); + post("next %f , prev %f", + exp((1./BPERO_OVER_LOG2) * (histvec[i].h_index + 97)), + exp((1./BPERO_OVER_LOG2) * (histvec[i].h_index + 95)) ); + } + } +#endif + + /* for each histogram peak, we now search back through the + * FFT peaks. A peak is a pitch if either there are several + * harmonics that match it, or else if (a) the fundamental is + * present, and (b) the sum of the powers of the contributing peaks + * is at least 1/100 of the total power. + * + * A peak is a contributor if its frequency is within 25 cents of + * a partial from 1 to 16. + * + * Finally, we have to be at least 5 bins in frequency, which + * corresponds to 2-1/5 periods fitting in the analysis window. + */ + + for (i = 0; i < npitch; i++) + { + float cumpow = 0, cumstrength = 0, freqnum = 0, freqden = 0; + int npartials = 0, nbelow8 = 0; + /* guessed-at frequency in bins */ + float putfreq = fexp((1.0f / BPERO_OVER_LOG2) * + (histvec[i].h_index + 96.0f)); + for (j = 0; j < npeak; j++) + { + float fpnum = peaklist[j].p_freq/putfreq; + int pnum = fpnum + 0.5f; + float fipnum = pnum; + float deviation; + if (pnum > 16 || pnum < 1) continue; + deviation = 1.0f - fpnum/fipnum; + if (deviation > -PARTIALDEVIANCE && deviation < PARTIALDEVIANCE) + { + /* + * we figure this is a partial since it's within 1/4 of + * a halftone of a multiple of the putative frequency. + */ + + float stdev, weight; + npartials++; + if (pnum < 8) nbelow8++; + cumpow += peaklist[j].p_pow; + cumstrength += fsqrt(fsqrt(peaklist[j].p_pow)); + stdev = (peaklist[j].p_width > MINBW ? + peaklist[j].p_width : MINBW); + weight = 1.0f / ((stdev*fipnum) * (stdev*fipnum)); + freqden += weight; + freqnum += weight * peaklist[j].p_freq/fipnum; +#if 1 + if (x->x_nprint) + { + post("peak %d partial %d f=%f w=%f", + j, pnum, peaklist[j].p_freq/fipnum, weight); + } +#endif + } +#if 1 + else if (x->x_nprint) post("peak %d partial %d dev %f", + j, pnum, deviation); +#endif + } + if ((nbelow8 < 4 || npartials < 7) && cumpow < 0.01f * total_power) + histvec[i].h_value = 0; + else + { + float pitchpow = (cumstrength * cumstrength) * + (cumstrength * cumstrength); + float freqinbins = freqnum/freqden; + /* check for minimum output frequency */ + + if (freqinbins < MINFREQINBINS) + histvec[i].h_value = 0; + else + { + /* we passed all tests... save the values we got */ + histvec[i].h_pitch = ftom(hzperbin * freqnum/freqden); + histvec[i].h_loud = (100.0f -DBFUDGE) + + (LOGTODB) * log(pitchpow/n); + } + } + } +#if 1 + if (x->x_nprint) + { + for (i = 0; i < npitch; i++) + { + if (histvec[i].h_value > 0) + post("index %d pit %f loud %f", histvec[i].h_index, + histvec[i].h_pitch, histvec[i].h_loud); + else post("-- cancelled --"); + } + } +#endif + + /* now try to find continuous pitch tracks that match the new + * pitches. First mark each peak unmatched. + */ + for (i = 0, hp1 = histvec; i < npitch; i++, hp1++) + hp1->h_used = 0; + + /* for each old pitch, try to match a new one to it. */ + for (i = 0, phist = x->x_hist; i < x->x_npitch; i++, phist++) + { + float thispitch = phist->h_pitches[oldphase]; + phist->h_pitch = 0; /* no output, thanks */ + phist->h_wherefrom = 0; + if (thispitch == 0.0f) continue; + for (j = 0, hp1 = histvec; j < npitch; j++, hp1++) + if ((hp1->h_value > 0) && hp1->h_pitch > thispitch - GLISS + && hp1->h_pitch < thispitch + GLISS) + { + phist->h_wherefrom = hp1; + hp1->h_used = 1; + } + } + for (i = 0, hp1 = histvec; i < npitch; i++, hp1++) + if ((hp1->h_value > 0) && !hp1->h_used) + { + for (j = 0, phist = x->x_hist; j < x->x_npitch; j++, phist++) + if (!phist->h_wherefrom) + { + phist->h_wherefrom = hp1; + phist->h_age = 0; + phist->h_noted = 0; + hp1->h_used = 1; + goto happy; + } + break; + happy: ; + } + /* copy the pitch info into the history vector */ + for (i = 0, phist = x->x_hist; i < x->x_npitch; i++, phist++) + { + if (phist->h_wherefrom) + { + phist->h_amps[newphase] = phist->h_wherefrom->h_loud; + phist->h_pitches[newphase] = + phist->h_wherefrom->h_pitch; + (phist->h_age)++; + } + else + { + phist->h_age = 0; + phist->h_amps[newphase] = phist->h_pitches[newphase] = 0; + } + } +#if 1 + if (x->x_nprint) + { + post("vibrato %d %f", x->x_vibbins, x->x_vibdepth); + for (i = 0, phist = x->x_hist; i < x->x_npitch; i++, phist++) + { + post("noted %f, age %d", phist->h_noted, phist->h_age); +#ifndef I860 + post("values %f %f %f %f %f", + phist->h_pitches[newphase], + phist->h_pitches[(newphase + HISTORY-1)%HISTORY], + phist->h_pitches[(newphase + HISTORY-2)%HISTORY], + phist->h_pitches[(newphase + HISTORY-3)%HISTORY], + phist->h_pitches[(newphase + HISTORY-4)%HISTORY]); +#endif + } + } +#endif + /* look for envelope attacks */ + + x->x_attackvalue = 0; + + if (x->x_peaked) + { + if (total_db > x->x_amphi) + { + int binlook = newphase - x->x_attackbins; + if (binlook < 0) binlook += HISTORY; + if (total_db > x->x_dbs[binlook] + x->x_attackthresh) + { + x->x_attackvalue = 1; + x->x_peaked = 0; + } + } + } + else + { + int binlook = newphase - x->x_attackbins; + if (binlook < 0) binlook += HISTORY; + if (x->x_dbs[binlook] > x->x_amphi && x->x_dbs[binlook] > total_db) + x->x_peaked = 1; + } + + /* for each current frequency track, test for a new note using a + * stability criterion. Later perhaps we should also do as in + * pitch~ and check for unstable notes a posteriori when + * there's a new attack with no note found since the last onset; + * but what's an attack &/or onset when we're polyphonic? + */ + + for (i = 0, phist = x->x_hist; i < x->x_npitch; i++, phist++) + { + /* + * if we've found a pitch but we've now strayed from it turn + * it off. + */ + if (phist->h_noted) + { + if (phist->h_pitches[newphase] > phist->h_noted + x->x_vibdepth + || phist->h_pitches[newphase] < phist->h_noted - x->x_vibdepth) + phist->h_noted = 0; + } + else + { + if (phist->h_wherefrom && phist->h_age >= x->x_vibbins) + { + float centroid = 0; + int not = 0; + for (j = 0, k = newphase; j < x->x_vibbins; j++) + { + centroid += phist->h_pitches[k]; + k--; + if (k < 0) k = HISTORY-1; + } + centroid /= x->x_vibbins; + for (j = 0, k = newphase; j < x->x_vibbins; j++) + { + /* calculate deviation from norm */ + float dev = centroid - phist->h_pitches[k]; + k--; + if (k < 0) k = HISTORY-1; + if (dev > x->x_vibdepth || + -dev > x->x_vibdepth) not = 1; + } + if (!not) + { + phist->h_pitch = phist->h_noted = centroid; + } + } + } + } + return; + +nopow: + for (i = 0; i < x->x_npitch; i++) + { + x->x_hist[i].h_pitch = x->x_hist[i].h_noted = + x->x_hist[i].h_pitches[newphase] = + x->x_hist[i].h_amps[newphase] = 0; + x->x_hist[i].h_age = 0; + } + x->x_peaked = 1; + x->x_dbage = 0; +} + +void sigfiddle_debug(t_sigfiddle *x) +{ + x->x_nprint = 1; +} + +void sigfiddle_print(t_sigfiddle *x) +{ + post("npoints %d,", 2 * x->x_hop); + post("amp-range %f %f,", x->x_amplo, x->x_amphi); + post("reattack %d %f,", x->x_attacktime, x->x_attackthresh); + post("vibrato %d %f", x->x_vibtime, x->x_vibdepth); + post("npartial %f", x->x_npartial); + post("auto %d", x->x_auto); +} + +void sigfiddle_amprange(t_sigfiddle *x, t_floatarg amplo, t_floatarg amphi) +{ + if (amplo < 0) amplo = 0; + if (amphi < amplo) amphi = amplo + 1; + x->x_amplo = amplo; + x->x_amphi = amphi; +} + +void sigfiddle_reattack(t_sigfiddle *x, + t_floatarg attacktime, t_floatarg attackthresh) +{ + if (attacktime < 0) attacktime = 0; + if (attackthresh <= 0) attackthresh = 1000; + x->x_attacktime = attacktime; + x->x_attackthresh = attackthresh; + x->x_attackbins = (x->x_sr * 0.001 * attacktime) / x->x_hop; + if (x->x_attackbins >= HISTORY) x->x_attackbins = HISTORY - 1; +} + +void sigfiddle_vibrato(t_sigfiddle *x, t_floatarg vibtime, t_floatarg vibdepth) +{ + if (vibtime < 0) vibtime = 0; + if (vibdepth <= 0) vibdepth = 1000; + x->x_vibtime = vibtime; + x->x_vibdepth = vibdepth; + x->x_vibbins = (x->x_sr * 0.001 * vibtime) / x->x_hop; + if (x->x_vibbins >= HISTORY) x->x_vibbins = HISTORY - 1; + if (x->x_vibbins < 1) x->x_vibbins = 1; +} + +void sigfiddle_npartial(t_sigfiddle *x, t_floatarg npartial) +{ + if (npartial < 0.1) npartial = 0.1; + x->x_npartial = npartial; +} + +void sigfiddle_auto(t_sigfiddle *x, t_floatarg f) +{ + x->x_auto = (f != 0); +} + +static void sigfiddle_freebird(t_sigfiddle *x) +{ + if (x->x_inbuf) + { + freebytes(x->x_inbuf, sizeof(float) * x->x_hop); + x->x_inbuf = 0; + } + if (x->x_lastanalysis) + { + freebytes(x->x_lastanalysis, + sizeof(float) * (2 * x->x_hop + 4 * FILTSIZE)); + x->x_lastanalysis = 0; + } + if (x->x_spiral) + { + freebytes(x->x_spiral, sizeof(float) * 2 * x->x_hop); + x->x_spiral = 0; + } + x->x_hop = 0; +} + +int sigfiddle_setnpoints(t_sigfiddle *x, t_floatarg fnpoints) +{ + int i, npoints = fnpoints; + sigfiddle_freebird(x); + if (npoints < MINPOINTS || npoints > MAXPOINTS) + { + error("fiddle~: npoints out of range; using %d", + npoints = DEFAULTPOINTS); + } + if (npoints != (1 << sigfiddle_ilog2(npoints))) + { + error("fiddle~: npoints not a power of 2; using %d", + npoints = (1 << sigfiddle_ilog2(npoints))); + } + x->x_hop = npoints >> 1; + if (!(x->x_inbuf = (float *)getbytes(sizeof(float) * x->x_hop))) + goto fail; + if (!(x->x_lastanalysis = (float *)getbytes( + sizeof(float) * (2 * x->x_hop + 4 * FILTSIZE)))) + goto fail; + if (!(x->x_spiral = (float *)getbytes(sizeof(float) * 2 * x->x_hop))) + goto fail; + for (i = 0; i < x->x_hop; i++) + x->x_inbuf[i] = 0; + for (i = 0; i < npoints + 4 * FILTSIZE; i++) + x->x_lastanalysis[i] = 0; + for (i = 0; i < x->x_hop; i++) + x->x_spiral[2*i] = cos((3.14159*i)/(npoints)), + x->x_spiral[2*i+1] = -sin((3.14159*i)/(npoints)); + x->x_phase = 0; + return (1); +fail: + sigfiddle_freebird(x); + return (0); +} + +int sigfiddle_doinit(t_sigfiddle *x, long npoints, long npitch, + long npeakanal, long npeakout) +{ + float *buf1, *buf2, *buf3; + t_peakout *buf4; + int i; + + if (!npeakanal && !npeakout) npeakanal = DEFNPEAK, npeakout = 0; + if (!npeakanal < 0) npeakanal = 0; + else if (npeakanal > MAXPEAK) npeakanal = MAXPEAK; + if (!npeakout < 0) npeakout = 0; + else if (npeakout > MAXPEAK) npeakout = MAXPEAK; + if (npitch <= 0) npitch = 0; + else if (npitch > MAXNPITCH) npitch = MAXNPITCH; + if (npeakanal && !npitch) npitch = 1; + if (!npoints) + npoints = DEFAULTPOINTS; + if (!sigfiddle_setnpoints(x, npoints)) + { + error("fiddle~: out of memory"); + return (0); + } + if (!(buf4 = (t_peakout *)getbytes(sizeof(*buf4) * npeakout))) + { + sigfiddle_freebird(x); + error("fiddle~: out of memory"); + return (0); + } + for (i = 0; i < npeakout; i++) + buf4[i].po_freq = buf4[i].po_amp = 0; + x->x_peakbuf = buf4; + + x->x_npeakout = npeakout; + x->x_npeakanal = npeakanal; + x->x_phase = 0; + x->x_histphase = 0; + x->x_sr = 44100; /* this and the next are filled in later */ + for (i = 0; i < MAXNPITCH; i++) + { + int j; + x->x_hist[i].h_pitch = x->x_hist[i].h_noted = 0; + x->x_hist[i].h_age = 0; + x->x_hist[i].h_wherefrom = 0; + x->x_hist[i].h_outlet = 0; + for (j = 0; j < HISTORY; j++) + x->x_hist[i].h_amps[j] = x->x_hist[i].h_pitches[j] = 0; + } + x->x_nprint = 0; + x->x_npitch = npitch; + for (i = 0; i < HISTORY; i++) x->x_dbs[i] = 0; + x->x_dbage = 0; + x->x_peaked = 0; + x->x_auto = 1; + x->x_amplo = DEFAMPLO; + x->x_amphi = DEFAMPHI; + x->x_attacktime = DEFATTACKTIME; + x->x_attackbins = 1; /* real value calculated afterward */ + x->x_attackthresh = DEFATTACKTHRESH; + x->x_vibtime = DEFVIBTIME; + x->x_vibbins = 1; /* real value calculated afterward */ + x->x_vibdepth = DEFVIBDEPTH; + x->x_npartial = 7; + x->x_attackvalue = 0; + return (1); +} + + /* formalities for JMAX */ + +#ifdef JMAX + +void sigfiddle_debug13(fts_object_t *o, int winlet, fts_symbol_t s, int ac, const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + sigfiddle_debug(x); +} + +void sigfiddle_print13(fts_object_t *o, int winlet, fts_symbol_t s, + int ac, const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + sigfiddle_print(x); +} + +void sigfiddle_amprange13(fts_object_t *o, int winlet, fts_symbol_t s, + int ac, const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + float lo = (float) fts_get_float_arg(ac, at, 0, 0); + float hi = (float) fts_get_float_arg(ac, at, 1, 0); + sigfiddle_amprange(x, lo, hi); +} + +void sigfiddle_reattack13(fts_object_t *o, int winlet, fts_symbol_t s, + int ac, const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + long msec = fts_get_float_arg(ac, at, 0, 0); + float db = (float) fts_get_float_arg(ac, at, 1, 0); + sigfiddle_reattack(x, msec, db); +} + +void sigfiddle_vibrato13(fts_object_t *o, int winlet, fts_symbol_t s, + int ac, const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + long msec = fts_get_float_arg(ac, at, 0, 0); + float halftones = (float) fts_get_float_arg(ac, at, 1, 0); + sigfiddle_vibrato(x, msec, halftones); +} + +void sigfiddle_npartial13(fts_object_t *o, int winlet, fts_symbol_t s, + int ac, const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + float npartial = (float) fts_get_float_arg(ac, at, 0, 0); + sigfiddle_npartial(x, npartial); +} + + +void ftl_sigfiddle(fts_word_t *a) +{ + t_sigfiddle *x = (t_sigfiddle *)fts_word_get_long(a); + float *in = (float *)fts_word_get_long(a + 1); + long n_tick = fts_word_get_long(a + 2); + + int count; + float *fp, *fp2; + for (count = 0, fp = x->x_inbuf + x->x_phase; + count < n_tick; count++) *fp++ = *in++; + if (fp == x->x_inbuf + x->x_hop) + { + sigfiddle_doit(x); + x->x_phase = 0; + fts_alarm_set_delay(&x->x_clock, 0L); /* output bang */ + fts_alarm_arm(&x->x_clock); + + if (x->x_nprint) x->x_nprint--; + } + else x->x_phase += n_tick; +} + +void sigfiddle_put(fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + fts_dsp_descr_t *dsp = (fts_dsp_descr_t *)fts_get_long_arg(ac, at, 0, 0); + fts_atom_t a[3]; + + x->x_sr = fts_dsp_get_input_srate(dsp, 0); + sigfiddle_reattack(x, x->x_attacktime, x->x_attackthresh); + sigfiddle_vibrato(x, x->x_vibtime, x->x_vibdepth); + + fts_set_long(a, (long)x); + fts_set_symbol(a+1, fts_dsp_get_input_name(dsp, 0)); + fts_set_long(a+2, fts_dsp_get_input_size(dsp, 0)); + dsp_add_funcall(dsp_symbol, 3, a); +} + +void sigfiddle_tick(fts_alarm_t *alarm, void *p) +{ + fts_object_t *o = (fts_object_t *)p; + t_sigfiddle *x = (t_sigfiddle *)p; + + int i; + t_pitchhist *ph; + fts_outlet_float(o, OUTLETpower, x->x_dbs[x->x_histphase]); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + { + fts_atom_t at[2]; + fts_set_float(at, ph->h_pitches[x->x_histphase]); + fts_set_float(at+1, ph->h_amps[x->x_histphase]); + fts_outlet_list(o, OUTLETmicropitch3 - i, 2, at); + } + if (x->x_attackvalue) fts_outlet_bang(o, OUTLETattack); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + if (ph->h_pitch) fts_outlet_float(o, OUTLETpitch, ph->h_pitch); +} + +static void sigfiddle_delete(fts_object_t *o, int winlet, fts_symbol_t *s, int ac, + const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + + fts_free(x->x_inbuf); + fts_free(x->x_lastanalysis); + fts_free(x->x_spiral); + dsp_list_remove(o); +} + +static void sigfiddle_init(fts_object_t *o, int winlet, fts_symbol_t *s, int ac, const fts_atom_t *at) +{ + t_sigfiddle *x = (t_sigfiddle *)o; + float *buf1, *buf2, *buf3; + int i, hop; + long npoints = fts_get_long_arg(ac, at, 1, 0); + long npitch = fts_get_long_arg(ac, at, 2, 0); + long npeakanal = fts_get_long_arg(ac, at, 3, 0); + long npeakout = fts_get_long_arg(ac, at, 4, 0); + + if (!sigfiddle_doinit(x, npoints, npitch, npeakanal, npeakout)) + { + post("fiddle~: initialization failed"); + return; + } + hop = npoints>>1; + if (fts_fft_declaresize(hop) != fts_Success) + post("fiddle~: bad FFT size"); + + fts_alarm_init(&(x->x_clock), 0, sigfiddle_tick, x); + dsp_list_insert(o); +} + +static fts_status_t sigfiddle_instantiate(fts_class_t *cl, int ac, + const fts_atom_t *at) +{ + int i; + fts_type_t a[5]; + + fts_class_init(cl, sizeof(t_sigfiddle), 1, 6, 0); /* 1 inlet + 6 outlets */ + + /* the system methods */ + + a[0] = fts_Symbol; + a[1] = fts_Long | fts_OptArg; + a[2] = fts_Long | fts_OptArg; + fts_method_define(cl, fts_SystemInlet, fts_s_init, sigfiddle_init, 3, a); + + fts_method_define(cl, fts_SystemInlet, fts_s_delete, sigfiddle_delete, 0, a); + a[0] = fts_Object; + fts_method_define(cl, fts_SystemInlet, fts_s_put, sigfiddle_put, 1, a); + + /* class' own methods */ + fts_method_define(cl, 0, fts_new_symbol("print"), sigfiddle_print13, 0, a); + fts_method_define(cl, 0, fts_new_symbol("debug"), sigfiddle_debug13, 0, a); + fts_method_define(cl, 0, fts_new_symbol("amp-range"), sigfiddle_amprange13, + 0, a); + fts_method_define(cl, 0, fts_new_symbol("reattack"), sigfiddle_reattack13, + 0, a); + fts_method_define(cl, 0, fts_new_symbol("vibrato"), sigfiddle_vibrato13, + 0, a); + fts_method_define(cl, 0, fts_new_symbol("npartial"), sigfiddle_npartial13, + 0, a); + + /* classes signal inlets */ + dsp_sig_inlet(cl, 0); /* declare signal input #0 */ + + /* classes outlets */ + a[0] = fts_Float; + fts_outlet_type_define(cl, OUTLETpitch, fts_s_float, 1, a); /* declare outlet #0 */ + fts_outlet_type_define(cl, OUTLETattack, fts_s_bang, 0, a); /* declare outlet #1 */ + a[0] = fts_VarArgs; + fts_outlet_type_define(cl, OUTLETmicropitch1, fts_s_list, 1, a); /* declare outlet #2 */ + fts_outlet_type_define(cl, OUTLETmicropitch2, fts_s_list, 1, a); /* declare outlet #3 */ + fts_outlet_type_define(cl, OUTLETmicropitch3, fts_s_list, 1, a); /* declare outlet #4 */ + a[0] = fts_Float; + fts_outlet_type_define(cl, OUTLETpower, fts_s_float, 1, a); /* declare outlet #5 */ + + dsp_symbol = fts_new_symbol("fiddle"); + dsp_declare_function(dsp_symbol, ftl_sigfiddle); + + /* DSP properties */ + + fts_class_put_prop(cl, fts_s_dsp_is_sink, fts_true); + + return(fts_Success); +} + +void fiddle_config(void) +{ + sys_log(fiddle_version); + fts_metaclass_create(fts_new_symbol(CLASSNAME), sigfiddle_instantiate, fts_always_equiv); +} + +fts_module_t fiddle_module = + {"fiddle", "sonic meat fiddle", fiddle_config, 0}; + +#endif /* JMAX */ + +#ifdef PD + +static t_int *fiddle_perform(t_int *w) +{ + t_float *in = (t_float *)(w[1]); + t_sigfiddle *x = (t_sigfiddle *)(w[2]); + int n = (int)(w[3]); + int count; + float *fp; + if (!x->x_hop) + goto nono; + for (count = 0, fp = x->x_inbuf + x->x_phase; count < n; count++) + *fp++ = *in++; + if (fp == x->x_inbuf + x->x_hop) + { + sigfiddle_doit(x); + x->x_phase = 0; + if (x->x_auto) clock_delay(x->x_clock, 0L); + if (x->x_nprint) x->x_nprint--; + } + else x->x_phase += n; +nono: + return (w+4); +} + +void sigfiddle_dsp(t_sigfiddle *x, t_signal **sp) +{ + x->x_sr = sp[0]->s_sr; + sigfiddle_reattack(x, x->x_attacktime, x->x_attackthresh); + sigfiddle_vibrato(x, x->x_vibtime, x->x_vibdepth); + dsp_add(fiddle_perform, 3, sp[0]->s_vec, x, sp[0]->s_n); +} + + /* This is the callback function for the clock, but also acts as + the "bang" method; you can leave "auto" on to get this called + automatically (the default) or turn auto off and bang it yourself. */ + +void sigfiddle_bang(t_sigfiddle *x) +{ + int i; + t_pitchhist *ph; + if (x->x_npeakout) + { + int npeakout = x->x_npeakout; + t_peakout *po; + for (i = 0, po = x->x_peakbuf; i < npeakout; i++, po++) + { + t_atom at[3]; + SETFLOAT(at, i+1); + SETFLOAT(at+1, po->po_freq); + SETFLOAT(at+2, po->po_amp); + outlet_list(x->x_peakout, 0, 3, at); + } + } + outlet_float(x->x_envout, x->x_dbs[x->x_histphase]); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + { + t_atom at[2]; + SETFLOAT(at, ph->h_pitches[x->x_histphase]); + SETFLOAT(at+1, ph->h_amps[x->x_histphase]); + outlet_list(ph->h_outlet, 0, 2, at); + } + if (x->x_attackvalue) outlet_bang(x->x_attackout); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + if (ph->h_pitch) outlet_float(x->x_noteout, ph->h_pitch); +} + +void sigfiddle_ff(t_sigfiddle *x) /* cleanup on free */ +{ + if (x->x_inbuf) + { + freebytes(x->x_inbuf, sizeof(float) * x->x_hop); + freebytes(x->x_lastanalysis, sizeof(float) * (2*x->x_hop + 4 * FILTSIZE)); + freebytes(x->x_spiral, sizeof(float) * 2*x->x_hop); + freebytes(x->x_peakbuf, sizeof(*x->x_peakbuf) * x->x_npeakout); + clock_free(x->x_clock); + } +} + +static t_class *sigfiddle_class; + +void *sigfiddle_new(t_floatarg npoints, t_floatarg npitch, + t_floatarg fnpeakanal, t_floatarg fnpeakout) +{ + t_sigfiddle *x = (t_sigfiddle *)pd_new(sigfiddle_class); + int i; + int npeakanal = fnpeakanal, npeakout = fnpeakout; + + + if (!sigfiddle_doinit(x, npoints, npitch, + npeakanal, npeakout)) + { + x->x_inbuf = 0; /* prevent the free routine from cleaning up */ + pd_free(&x->x_ob.ob_pd); + return (0); + } + x->x_noteout = outlet_new(&x->x_ob, gensym("float")); + x->x_attackout = outlet_new(&x->x_ob, gensym("bang")); + for (i = 0; i < x->x_npitch; i++) + x->x_hist[i].h_outlet = outlet_new(&x->x_ob, gensym("list")); + x->x_envout = outlet_new(&x->x_ob, gensym("float")); + if (x->x_npeakout) + x->x_peakout = outlet_new(&x->x_ob, gensym("list")); + else x->x_peakout = 0; + x->x_clock = clock_new(&x->x_ob.ob_pd, (t_method)sigfiddle_bang); + return (x); +} + +void fiddle_tilde_setup(void) +{ + sigfiddle_class = class_new(gensym("fiddle~"), (t_newmethod)sigfiddle_new, + (t_method)sigfiddle_ff, sizeof(t_sigfiddle), 0, + A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_dsp, + gensym("dsp"), 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_debug, + gensym("debug"), 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_setnpoints, + gensym("npoints"), A_FLOAT, 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_amprange, + gensym("amp-range"), A_FLOAT, A_FLOAT, 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_reattack, + gensym("reattack"), A_FLOAT, A_FLOAT, 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_vibrato, + gensym("vibrato"), A_FLOAT, A_FLOAT, 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_npartial, + gensym("npartial"), A_FLOAT, 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_auto, + gensym("auto"), A_FLOAT, 0); + class_addmethod(sigfiddle_class, (t_method)sigfiddle_print, + gensym("print"), 0); + class_addmethod(sigfiddle_class, nullfn, gensym("signal"), 0); + class_addbang(sigfiddle_class, sigfiddle_bang); + class_addcreator((t_newmethod)sigfiddle_new, gensym("fiddle"), + A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, A_DEFFLOAT, 0); + post(fiddle_version); +} + +void fiddle_setup(void) +{ + fiddle_tilde_setup(); +} +#endif /* PD */ + +#ifdef MAX26 + +void cu_fiddle(float *in1, t_sigfiddle *x, int n) +{ + int count; + float *fp, *fp2; + for (count = 0, fp = x->x_inbuf + x->x_phase; + count < n; count++) *fp++ = *in1++; + if (fp == x->x_inbuf + x->x_hop) + { + sigfiddle_doit(x); + x->x_phase = 0; + if (x->x_auto) clock_delay(x->x_clock, 0L); + if (x->x_nprint) x->x_nprint--; + } + else x->x_phase += n; +} + +void sigfiddle_put(t_sigfiddle *x, long whether) +{ + if (whether) + { + u_stdout(x); + x->x_sr = x->x_io[0]->s_sr; + sigfiddle_reattack(x, x->x_attacktime, x->x_attackthresh); + sigfiddle_vibrato(x, x->x_vibtime, x->x_vibdepth); + dspchain_addc(cu_fiddle, 3, + x->x_io[0]->s_shit, x, x->x_io[0]->s_n); + } +} + +void sigfiddle_tick(t_sigfiddle *x) /* callback function for the clock */ +{ + int i; + t_pitchhist *ph; + outlet_float(x->x_envout, x->x_dbs[x->x_histphase]); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + { + t_atom at[2]; + SETFLOAT(at, ph->h_pitches[x->x_histphase]); + SETFLOAT(at+1, ph->h_amps[x->x_histphase]); + outlet_list(ph->h_outlet, NIL, 2, at); + } + if (x->x_attackvalue) outlet_bang(x->x_attackout); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + if (ph->h_pitch) outlet_float(x->x_noteout, ph->h_pitch); +} + +void sigfiddle_ff(t_sigfiddle *x) /* cleanup on free */ +{ + if (x->x_inbuf) + { + freebytes(x->x_inbuf, sizeof(float) * x->x_hop); + freebytes(x->x_lastanalysis, sizeof(float) * (2*x->x_hop + 4 * FILTSIZE)); + freebytes(x->x_spiral, sizeof(float) * 2*x->x_hop); + clock_free(x->x_clock); + u_clean(x); + } +} + +t_externclass *sigfiddle_class; + +void *sigfiddle_new(long npoints, long npitch, + long npeakanal, long npeakout) +{ + t_sigfiddle *x = (t_sigfiddle *)obj_new(&sigfiddle_class, 0); + int i; + + if (!sigfiddle_doinit(x, npoints, npitch, npeakanal, npeakout)) + { + x->x_inbuf = 0; /* prevent the free routine from cleaning up */ + obj_free(x); + return (0); + } + u_setup(x, IN1, OUT0); + x->x_envout = outlet_new(x, gensym("float")); + for (i = 0; i < x->x_npitch; i++) + x->x_hist[i].h_outlet = outlet_new(x, gensym("list")); + x->x_attackout = outlet_new(x, gensym("bang")); + x->x_noteout = outlet_new(x, gensym("float")); + x->x_clock = clock_new(x, sigfiddle_tick); + return (x); +} + +void fiddle_setup() +{ + c_extern(&sigfiddle_class, sigfiddle_new, sigfiddle_ff, + gensym("fiddle"), sizeof(t_sigfiddle), 0, A_DEFLONG, A_DEFLONG, + A_DEFLONG, A_DEFLONG, 0); + c_addmess(sigfiddle_put, gensym("put"), A_CANT, 0); + c_addmess(sigfiddle_debug, gensym("debug"), 0); + c_addmess(sigfiddle_amprange, gensym("amp-range"), A_FLOAT, A_FLOAT, 0); + c_addmess(sigfiddle_reattack, gensym("reattack"), A_FLOAT, A_FLOAT, 0); + c_addmess(sigfiddle_vibrato, gensym("vibrato"), A_LONG, A_FLOAT, 0); + c_addmess(sigfiddle_npartial, gensym("npartial"), A_FLOAT, 0); + c_addmess(sigfiddle_print, gensym("print"), 0); + u_inletmethod(0); /* one signal input */ +#ifdef MAX + post(fiddle_version); +#endif +} + +#endif /* MAX26 */ + +/************* Beginning of MSP Code ******************************/ + +#ifdef MSP + +static t_int *fiddle_perform(t_int *w) +{ + t_float *in = (t_float *)(w[1]); + t_sigfiddle *x = (t_sigfiddle *)(w[2]); + int n = (int)(w[3]); + int count,inc = x->x_downsample; + float *fp; + + if (x->x_obj.z_disabled) + goto skip; + for (count = 0, fp = x->x_inbuf + x->x_phase; count < n; count+=inc) { + *fp++ = *in; + in += inc; + } + if (fp == x->x_inbuf + x->x_hop) + { + sigfiddle_doit(x); + x->x_phase = 0; + if (x->x_auto) clock_delay(x->x_clock, 0L); + if (x->x_nprint) x->x_nprint--; + } + else x->x_phase += n; +skip: + return (w+4); +} + +void sigfiddle_dsp(t_sigfiddle *x, t_signal **sp) +{ + if (sp[0]->s_n > x->x_hop) { + x->x_downsample = sp[0]->s_n / x->x_hop; + post("* warning: fiddle~: will downsample input by %ld",x->x_downsample); + x->x_sr = sp[0]->s_sr / x->x_downsample; + } else { + x->x_downsample = 1; + x->x_sr = sp[0]->s_sr; + } + sigfiddle_reattack(x, x->x_attacktime, x->x_attackthresh); + sigfiddle_vibrato(x, x->x_vibtime, x->x_vibdepth); + dsp_add(fiddle_perform, 3, sp[0]->s_vec, x, sp[0]->s_n); +} + +void sigfiddle_tick(t_sigfiddle *x) /* callback function for the clock MSP*/ +{ + int i; + t_pitchhist *ph; + if (x->x_npeakout) + { + int npeakout = x->x_npeakout; + t_peakout *po; + for (i = 0, po = x->x_peakbuf; i < npeakout; i++, po++) + { + t_atom at[3]; + SETINT(at, i+1); + SETFLOAT(at+1, po->po_freq); + SETFLOAT(at+2, po->po_amp); + outlet_list(x->x_peakout, 0, 3, at); + } + } + outlet_float(x->x_envout, x->x_dbs[x->x_histphase]); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + { + t_atom at[2]; + SETFLOAT(at, ph->h_pitches[x->x_histphase]); + SETFLOAT(at+1, ph->h_amps[x->x_histphase]); + outlet_list(ph->h_outlet, 0, 2, at); + } + if (x->x_attackvalue) outlet_bang(x->x_attackout); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + if (ph->h_pitch) outlet_float(x->x_noteout, ph->h_pitch); +} + +void sigfiddle_bang(t_sigfiddle *x) // MSP +{ + int i; + t_pitchhist *ph; + if (x->x_npeakout) + { + int npeakout = x->x_npeakout; + t_peakout *po; + for (i = 0, po = x->x_peakbuf; i < npeakout; i++, po++) + { + t_atom at[3]; + SETLONG(at, i+1); + SETFLOAT(at+1, po->po_freq); + SETFLOAT(at+2, po->po_amp); + outlet_list(x->x_peakout, 0, 3, at); + } + } + outlet_float(x->x_envout, x->x_dbs[x->x_histphase]); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + { + t_atom at[2]; + SETFLOAT(at, ph->h_pitches[x->x_histphase]); + SETFLOAT(at+1, ph->h_amps[x->x_histphase]); + outlet_list(ph->h_outlet, 0, 2, at); + } + if (x->x_attackvalue) outlet_bang(x->x_attackout); + for (i = 0, ph = x->x_hist; i < x->x_npitch; i++, ph++) + if (ph->h_pitch) outlet_float(x->x_noteout, ph->h_pitch); +} + + +void sigfiddle_ff(t_sigfiddle *x) /* cleanup on free MSP */ +{ + + if (x->x_inbuf) + { + t_freebytes(x->x_inbuf, sizeof(float) * x->x_hop); + t_freebytes(x->x_lastanalysis, sizeof(float) * (2*x->x_hop + 4 * +FILTSIZE)); + t_freebytes(x->x_spiral, sizeof(float) * 2*x->x_hop); + t_freebytes(x->x_peakbuf, sizeof(*x->x_peakbuf) * x->x_npeakout); + //clock_free(x->x_clock); + } + dsp_free((t_pxobject *)x); // Free the object +} + +void *sigfiddle_class; + +void *sigfiddle_new(long npoints, long npitch, // MSP + long npeakanal, long npeakout) +{ + t_sigfiddle *x = (t_sigfiddle *)newobject(sigfiddle_class); + int i; + + if (!sigfiddle_doinit(x, npoints, npitch, npeakanal, npeakout)) + // MSP + { + x->x_inbuf = 0; /* prevent the free routine from cleaning up */ + return (0); + } + // post("npeak %d, npitch %d", npeakout, npitch); + // set up the inlets and outlets. + dsp_setup((t_pxobject *)x,1); // 1 input + + x->x_clock = clock_new(x, (method)sigfiddle_tick); + if (x->x_npeakout) + x->x_peakout = listout((t_object *)x); // listout? + else x->x_peakout = 0; + x->x_envout = floatout((t_object *)x); + for (i = 0; i < x->x_npitch; i++) + x->x_hist[i].h_outlet = listout((t_object *)x); + x->x_attackout = bangout((t_object *)x); + x->x_noteout = floatout((t_object *)x); + return (x); + + +} + +void main() // this can be called fiddle_setup if that name is the "Main" +in PPC Linker prefs +{ + setup(&sigfiddle_class, sigfiddle_new, (method)sigfiddle_ff, + (short)sizeof(t_sigfiddle), 0L, A_DEFLONG, A_DEFLONG, +A_DEFLONG, A_DEFLONG, 0); + addmess((method)sigfiddle_dsp, "dsp", + A_CANT, 0); + addmess((method)sigfiddle_debug, "debug", 0); + addmess((method)sigfiddle_setnpoints, "npoints", A_FLOAT, 0); + addmess((method)sigfiddle_amprange, "amp-range", A_FLOAT, A_FLOAT, 0); + addmess((method)sigfiddle_reattack, "reattack", A_FLOAT, A_FLOAT, 0); + addmess((method)sigfiddle_vibrato, "vibrato", A_FLOAT, +A_FLOAT, 0); + addmess((method)sigfiddle_npartial, "npartial", A_FLOAT, 0); + addmess((method)sigfiddle_auto, "auto", + A_FLOAT, 0); + addmess((method)sigfiddle_print, "print", 0); + addmess((method)sigfiddle_assist, "assist", + A_CANT, 0); + addbang((method)sigfiddle_bang); + dsp_initclass(); + rescopy('STR#',3748); + post(fiddle_version); +} + +void sigfiddle_assist(t_sigfiddle *x, void *b, long m, long a, char *s) +{ + assist_string(3748,m,a,1,2,s); +} + +void msp_fft(float *buf, long np, long inv) +{ + float *src,*real,*rp,*imag,*ip; + long i; + + // because this fft algorithm uses separate real and imaginary + // buffers + // we must split the real and imaginary parts into two buffers, + // then do the opposite on output + // a more ambitious person would either do an in-place conversion + // or rewrite the fft algorithm + + real = rp = msp_ffttemp; + imag = ip = real + MAXPOINTS; + src = buf; + for (i = 0; i < np; i++) { + *rp++ = *src++; + *ip++ = *src++; + } + if (inv) + ifft(np,real,imag); + else + fft(np,real,imag); + rp = real; + ip = imag; + src = buf; + for (i = 0; i < np; i++) { + *src++ = *rp++; + *src++ = *ip++; + } +} + +#endif /* MSP */ diff --git a/externals/signal/fiddle~/help-fiddle~.pd b/externals/signal/fiddle~/help-fiddle~.pd new file mode 100644 index 00000000..f2330bfa --- /dev/null +++ b/externals/signal/fiddle~/help-fiddle~.pd @@ -0,0 +1,142 @@ +#N canvas 93 26 980 745 10; +#X obj 262 522 phasor~; +#X obj 531 616 unpack; +#X floatatom 531 666 0 0 0 0 - - -; +#X msg 437 449 print; +#X obj 262 500 sig~; +#X floatatom 262 478 0 0 0 0 - - -; +#X obj 262 456 mtof; +#X floatatom 262 434 0 0 0 0 - - -; +#X floatatom 545 643 0 0 0 0 - - -; +#X obj 531 576 route 1 2 3 4; +#X obj 614 616 unpack; +#X floatatom 614 666 0 0 0 0 - - -; +#X floatatom 628 643 0 0 0 0 - - -; +#X obj 698 616 unpack; +#X floatatom 698 666 0 0 0 0 - - -; +#X floatatom 712 643 0 0 0 0 - - -; +#X obj 389 616 unpack; +#X floatatom 389 666 0 0 0 0 - - -; +#X floatatom 403 643 0 0 0 0 - - -; +#X obj 334 545 *~; +#X obj 322 394 loadbang; +#X obj 353 522 sig~; +#X floatatom 353 500 0 0 0 0 - - -; +#X msg 322 478 1; +#X msg 353 478 0; +#X floatatom 466 666 0 0 0 0 - - -; +#X obj 281 666 print attack; +#X obj 190 666 print pitch; +#X msg 555 45 \; pd dsp 1; +#X text 460 39 click here; +#X text 460 61 to start DSP; +#X text 226 4 FIDDLE - pitch estimator and sinusoidal peak finder; +#X text 8 70 The Fiddle object estimates the pitch and amplitude of +an incoming sound \, both continuously and as a stream of discrete +"note" events. Fiddle optionally outputs a list of detected sinusoidal +peaks used to make the pitch determination. Fiddle is described theoretically +in the 1998 ICMC proceedings \, reprinted on http://man104nfs.ucsd.edu/~mpuckett. +; +#X text 8 170 Fiddle's creation arguments specify an analysis window +size \, the maximum polyphony (i.e. \, the number of simultaneous "pitches" +to try to find) \, the number of peaks in the spectrum to consider +\, and the number of peaks \, if any \, to output "raw." The outlets +give discrete pitch (a number) \, detected attacks in the amplitude +envelope (a bang) \, one or more voices of continuous pitch and amplitude +\, overall amplitude \, and optionally a sequence of messages with +the peaks.; +#X text 8 296 The analysis hop size is half the window size so in the +example shown here \, one analysis is done every 512 samples (11.6 +msec at 44K1) \, and the analysis uses the most recent 1024 samples +(23.2 msec at 44K1). The minimum frequency that Fiddle will report +is 2-1/2 cycles per analysis windows \, or about 108 Hz. (just below +MIDI 45.); +#X text 669 535 number of pitch outlets (1-3 \, default 1); +#X text 669 557 number of peaks to find (1-100 \, default 20); +#X text 669 579 number of peaks to output (default 0.); +#X msg 441 107 amp-range 40 50; +#X msg 439 227 reattack 100 10; +#X msg 438 282 npartial 7; +#X msg 438 170 vibrato 50 0.5; +#X text 560 91 a low and high amplitude threshold: if signal amplitude +is below the low threshold \, no pitches or peaks are output. The high +threshold is a minimum at which "cooked" outputs may appear.; +#X text 560 152 A period in milliseconds (50) over which the raw pitch +may not deviate more than an interval in half-tones (0.5) from the +average pitch to report it as a note to the "cooked" pitch outlet. +; +#X text 560 213 A period in milliseconds (100) over which a re-attack +is reported if the amplitude rises more than (1) dB. The re-attack +will result in a "bang" in the attack outlet and may give rise to repeated +notes in the cooked pitch output.; +#X text 142 432 test input pitch; +#X text 330 444 test input; +#X text 330 457 amplitude; +#X obj 410 545 fiddle~ 1024 1 20 3; +#X text 538 690 individual sinusoidal components; +#X text 466 688 amplitude; +#X text 476 703 (dB); +#X text 389 688 raw pitch; +#X text 376 712 and amplitude; +#X text 364 729 (up to 3 outputs); +#X text 287 686 bang on; +#X text 287 708 attack; +#X text 185 686 cooked pitch; +#X text 202 703 output; +#X text 545 545 ------ arguments:; +#X msg 262 412 57; +#X msg 440 331 auto 1; +#X msg 440 353 auto 0; +#X msg 439 418 bang; +#X text 561 416 poll current values --- useful if not in auto mode +\,; +#X text 560 274 Higher partials are weighed less strongly than lower +ones in determining the pitch. This specifies the number of the partial +(7) which will be weighted half as strongly as the fundamental.; +#X text 560 335 start and stop "auto" mode (on by default.) If off +\, output only appears on "bang" (poll mode).; +#X text 561 448 print out all settings; +#X text 669 513 window size (128-2048 \, default 1024); +#X msg 440 375 npoints 2048; +#X text 562 384 number of points in analysis window (power of 2 \, +128-2048); +#X msg 439 396 npoints 1024; +#X connect 0 0 19 0; +#X connect 1 0 2 0; +#X connect 1 1 8 0; +#X connect 3 0 48 0; +#X connect 4 0 0 0; +#X connect 5 0 4 0; +#X connect 6 0 5 0; +#X connect 7 0 6 0; +#X connect 9 0 1 0; +#X connect 9 1 10 0; +#X connect 9 2 13 0; +#X connect 10 0 11 0; +#X connect 10 1 12 0; +#X connect 13 0 14 0; +#X connect 13 1 15 0; +#X connect 16 0 17 0; +#X connect 16 1 18 0; +#X connect 19 0 48 0; +#X connect 20 0 60 0; +#X connect 20 0 23 0; +#X connect 21 0 19 1; +#X connect 22 0 21 0; +#X connect 23 0 22 0; +#X connect 24 0 22 0; +#X connect 38 0 48 0; +#X connect 39 0 48 0; +#X connect 40 0 48 0; +#X connect 41 0 48 0; +#X connect 48 0 27 0; +#X connect 48 1 26 0; +#X connect 48 2 16 0; +#X connect 48 3 25 0; +#X connect 48 4 9 0; +#X connect 60 0 7 0; +#X connect 61 0 48 0; +#X connect 62 0 48 0; +#X connect 63 0 48 0; +#X connect 69 0 48 0; +#X connect 71 0 48 0; -- cgit v1.2.1