/* Copyright (c) 1997-1999 Miller Puckette. * For information on usage and redistribution, and for a DISCLAIMER OF ALL * WARRANTIES, see the file, "LICENSE.txt," in this distribution. */ /* These routines build a copy of the DSP portion of a graph, which is then sorted into a linear list of DSP operations which are added to the DSP duty cycle called by the scheduler. Once that's been done, we delete the copy. The DSP objects are represented by "ugenbox" structures which are parallel to the DSP objects in the graph and have vectors of siginlets and sigoutlets which record their interconnections. */ /* hacked to run subpatches with different power-of-2 samplerates - mfg.gfd.uil IOhannes */ #define PD_PLUSPLUS_FACE #include "desire.h" #include #include /* T.Grill - include SIMD functionality */ #include "m_simd.h" extern t_class *vinlet_class, *voutlet_class, *canvas_class; t_sample *obj_findsignalscalar(t_object *x, int m); static int ugen_loud; static t_int *dsp_chain; static int dsp_chainsize; struct t_vinlet; struct t_voutlet; void vinlet_dspprolog(t_vinlet *x, t_signal **parentsigs, int myvecsize, int calcsize, int phase, int period, int frequency, int downsample, int upsample, int reblock, int switched); void voutlet_dspprolog(t_voutlet *x, t_signal **parentsigs, int myvecsize, int calcsize, int phase, int period, int frequency, int downsample, int upsample, int reblock, int switched); void voutlet_dspepilog(t_voutlet *x, t_signal **parentsigs, int myvecsize, int calcsize, int phase, int period, int frequency, int downsample, int upsample, int reblock, int switched); /* zero out a vector */ t_int *zero_perform(t_int *w) { t_float *out = (t_float *)w[1]; int n = int(w[2]); while (n--) *out++ = 0; return w+3; } t_int *zero_perf8(t_int *w) { t_float *out = (t_float *)w[1]; int n = int(w[2]); for (; n; n -= 8, out += 8) { out[0] = 0; out[1] = 0; out[2] = 0; out[3] = 0; out[4] = 0; out[5] = 0; out[6] = 0; out[7] = 0; } return w+3; } void dsp_add_zero(t_sample *out, int n) { if (n&7) dsp_add(zero_perform, 2, out, n); else if(SIMD_CHECK1(n,out)) dsp_add(zero_perf_simd, 2, out, n); else dsp_add(zero_perf8, 2, out, n); } /* ---------------------------- block~ ----------------------------- */ /* The "block~ object maintains the containing canvas's DSP computation, calling it at a super- or sub-multiple of the containing canvas's calling frequency. The block~'s creation arguments specify block size and overlap. Block~ does no "dsp" computation in its own right, but it adds prolog and epilog code before and after the canvas's unit generators. A subcanvas need not have a block~ at all; if there's none, its ugens are simply put on the list without any prolog or epilog code. Block~ may be invoked as switch~, in which case it also acts to switch the subcanvas on and off. The overall order of scheduling for a subcanvas is thus, inlet and outlet prologue code (1) block prologue (2) the objects in the subcanvas, including inlets and outlets block epilogue (2) outlet epilogue code (2) where (1) means, "if reblocked" and (2) means, "if reblocked or switched". If we're reblocked, the inlet prolog and outlet epilog code takes care of overlapping and buffering to deal with vector size changes. If we're switched but not reblocked, the inlet prolog is not needed, and the output epilog is ONLY run when the block is switched off; in this case the epilog code simply copies zeros to all signal outlets. */ static int dsp_phase; static t_class *block_class; struct t_block : t_object { int vecsize; /* size of audio signals in this block */ int calcsize; /* number of samples actually to compute */ int overlap; int phase; /* from 0 to period-1; when zero we run the block */ int period; /* submultiple of containing canvas */ int frequency; /* supermultiple of comtaining canvas */ int count; /* number of times parent block has called us */ int chainonset; /* beginning of code in DSP chain */ int blocklength; /* length of dspchain for this block */ int epiloglength; /* length of epilog */ char switched; /* true if we're acting as a a switch */ char switchon; /* true if we're switched on */ char reblock; /* true if inlets and outlets are reblocking */ int upsample; /* IOhannes: upsampling-factor */ int downsample; /* IOhannes: downsampling-factor */ int x_return; /* stop right after this block (for one-shots) */ }; static void block_set(t_block *x, t_floatarg fvecsize, t_floatarg foverlap, t_floatarg fupsample); static void *block_new(t_floatarg fcalcsize, t_floatarg foverlap, t_floatarg fupsample) /* IOhannes */ { t_block *x = (t_block *)pd_new(block_class); x->phase = 0; x->period = 1; x->frequency = 1; x->switched = 0; x->switchon = 1; block_set(x, fcalcsize, foverlap, fupsample); return x; } static void block_set(t_block *x, t_floatarg fcalcsize, t_floatarg foverlap, t_floatarg fupsample) { int upsample, downsample; /* IOhannes */ int calcsize = (int)fcalcsize; int overlap = (int)foverlap; int dspstate = canvas_suspend_dsp(); if (overlap < 1) overlap = 1; if (calcsize < 0) calcsize = 0; /* this means we'll get it from parent later. */ /* IOhannes { */ if (fupsample <= 0) upsample = downsample = 1; else if (fupsample >= 1) { upsample = (int)fupsample; downsample = 1; } else { downsample = int(1.0 / fupsample); upsample = 1; } /* } IOhannes */ /* vecsize is smallest power of 2 large enough to hold calcsize */ int vecsize = 0; if (calcsize) { vecsize = (1 << ilog2(calcsize)); if (vecsize != calcsize) vecsize *= 2; } if (vecsize && vecsize != (1 << ilog2(vecsize))) {error("block~: vector size not a power of 2"); vecsize = 64;} if ( overlap != (1 << ilog2(overlap))) {error("block~: overlap not a power of 2"); overlap = 1;} /* IOhannes { */ if (downsample != (1 << ilog2(downsample))) {error("block~: downsampling not a power of 2"); downsample = 1;} if ( upsample != (1 << ilog2( upsample))) {error("block~: upsampling not a power of 2"); upsample = 1;} /* } IOhannes */ x->calcsize = calcsize; x->vecsize = vecsize; x->overlap = overlap; /* IOhannes { */ x->upsample = upsample; x->downsample = downsample; /* } IOhannes */ canvas_resume_dsp(dspstate); } static void *switch_new(t_floatarg fvecsize, t_floatarg foverlap, t_floatarg fupsample) /* IOhannes */ { t_block *x = (t_block *)(block_new(fvecsize, foverlap, fupsample)); /* IOhannes */ x->switched = 1; x->switchon = 0; return x; } static void block_float(t_block *x, t_floatarg f) { if (x->switched) x->switchon = f!=0; } static void block_bang(t_block *x) { if (x->switched && !x->switchon) { x->x_return = 1; for (t_int *ip = dsp_chain + x->chainonset; ip; ) ip = t_perfroutine(*ip)(ip); x->x_return = 0; } else error("bang to block~ or on-state switch~ has no effect"); } #define PROLOGCALL 2 #define EPILOGCALL 2 static t_int *block_prolog(t_int *w) { t_block *x = (t_block *)w[1]; int phase = x->phase; /* if we're switched off, jump past the epilog code */ if (!x->switchon) return w+x->blocklength; if (phase) { phase++; if (phase == x->period) phase = 0; x->phase = phase; return w+x->blocklength; /* skip block; jump past epilog */ } else { x->count = x->frequency; x->phase = (x->period > 1 ? 1 : 0); return w+PROLOGCALL; /* beginning of block is next ugen */ } } static t_int *block_epilog(t_int *w) { t_block *x = (t_block *)w[1]; int count = x->count - 1; if (x->x_return) return 0; if (!x->reblock) return w+x->epiloglength+EPILOGCALL; if (count) { x->count = count; return w - (x->blocklength - (PROLOGCALL + EPILOGCALL)); /* go to ugen after prolog */ } else return w+EPILOGCALL; } static void block_dsp(t_block *x, t_signal **sp) {/* do nothing here */} void block_tilde_setup() { block_class = class_new2("block~", (t_newmethod)block_new, 0, sizeof(t_block), 0,"FFF"); class_addcreator2("switch~",(t_newmethod)switch_new,"FFF"); class_addmethod2(block_class, (t_method)block_set,"set","FFF"); class_addmethod2(block_class, (t_method)block_dsp,"dsp",""); class_addfloat(block_class, block_float); class_addbang(block_class, block_bang); } /* ------------------ DSP call list ----------------------- */ static t_int dsp_done(t_int *w) {return 0;} void dsp_add(t_perfroutine f, int n, ...) { va_list ap; va_start(ap, n); int newsize = dsp_chainsize + n+1; dsp_chain = (t_int *)resizebytes(dsp_chain, dsp_chainsize * sizeof (t_int), newsize * sizeof (t_int)); dsp_chain[dsp_chainsize-1] = (t_int)f; for (int i=0; i>= 1; } return r; } /* list of signals which can be reused, sorted by buffer size */ static t_signal *signal_freelist[MAXLOGSIG+1]; /* list of reusable "borrowed" signals (which don't own sample buffers) */ static t_signal *signal_freeborrowed; /* list of all signals allocated (not including "borrowed" ones) */ static t_signal *signal_usedlist; /* call this when DSP is stopped to free all the signals */ void signal_cleanup() { t_signal *sig; while ((sig = signal_usedlist)) { signal_usedlist = sig->nextused; if (!sig->isborrowed) { #ifndef VECTORALIGNMENT free(sig->v); #else freealignedbytes(sig->v, sig->vecsize * sizeof (*sig->v)); #endif } free(sig); } for (int i=0; i<=MAXLOGSIG; i++) signal_freelist[i] = 0; signal_freeborrowed = 0; } /* mark the signal "reusable." */ extern "C" void signal_makereusable(t_signal *sig) { int logn = ilog2(sig->vecsize); #if 1 for (t_signal *s5 = signal_freeborrowed; s5; s5 = s5->nextfree) {if (s5 == sig) {bug("signal_free 3"); return;}} for (t_signal *s5 = signal_freelist[logn]; s5; s5 = s5->nextfree) {if (s5 == sig) {bug("signal_free 4"); return;}} #endif if (ugen_loud) post("free %lx: %d", long(sig), sig->isborrowed); if (sig->isborrowed) { /* if the signal is borrowed, decrement the borrowed-from signal's reference count, possibly marking it reusable too */ t_signal *s2 = sig->borrowedfrom; if ((s2 == sig) || !s2) bug("signal_free"); s2->refcount--; if (!s2->refcount) signal_makereusable(s2); sig->nextfree = signal_freeborrowed; signal_freeborrowed = sig; } else { /* if it's a real signal (not borrowed), put it on the free list so we can reuse it. */ if (signal_freelist[logn] == sig) bug("signal_free 2"); sig->nextfree = signal_freelist[logn]; signal_freelist[logn] = sig; } } /* reclaim or make an audio signal. If n is zero, return a "borrowed" signal whose buffer and size will be obtained later via signal_setborrowed(). */ t_signal *signal_new(int n, float sr) { int vecsize = 0; t_signal *ret, **whichlist; int logn = ilog2(n); if (n) { if ((vecsize = (1< MAXLOGSIG) bug("signal buffer too large"); whichlist = signal_freelist + logn; } else whichlist = &signal_freeborrowed; /* first try to reclaim one from the free list */ ret = *whichlist; if (ret) *whichlist = ret->nextfree; else { /* LATER figure out what to do for out-of-space here! */ ret = (t_signal *)t_getbytes(sizeof *ret); if (n) { #ifndef VECTORALIGNMENT ret->v = (t_sample *)getbytes(vecsize * sizeof (*ret->v)); #else /* T.Grill - make signal vectors aligned! */ ret->v = (t_sample *)getalignedbytes(vecsize * sizeof (*ret->v)); #endif ret->isborrowed = 0; } else { ret->v = 0; ret->isborrowed = 1; } ret->nextused = signal_usedlist; signal_usedlist = ret; } ret->n = n; ret->vecsize = vecsize; ret->sr = sr; ret->refcount = 0; ret->borrowedfrom = 0; if (ugen_loud) post("new %lx: %d", long(ret), ret->isborrowed); return ret; } static t_signal *signal_newlike(const t_signal *sig) {return signal_new(sig->n, sig->sr);} extern "C" void signal_setborrowed(t_signal *sig, t_signal *sig2) { if (!sig->isborrowed || sig->borrowedfrom) bug("signal_setborrowed"); if (sig == sig2) bug("signal_setborrowed 2"); sig->borrowedfrom = sig2; sig->v = sig2->v; sig->n = sig2->n; sig->vecsize = sig2->vecsize; } int signal_compatible(t_signal *s1, t_signal *s2) {return s1->n == s2->n && s1->sr == s2->sr;} /* ------------------ ugen ("unit generator") sorting ----------------- */ struct t_ugenbox { struct t_siginlet *in; int nin; struct t_sigoutlet *out; int nout; int u_phase; t_ugenbox *next; t_object *obj; int done; }; struct t_siginlet { int nconnect; int ngot; t_signal *signal; }; struct t_sigoutconnect { t_ugenbox *who; int inno; t_sigoutconnect *next; }; struct t_sigoutlet { int nconnect; int nsent; t_signal *signal; t_sigoutconnect *connections; }; struct t_dspcontext { t_ugenbox *ugenlist; t_dspcontext *parentcontext; int ninlets; int noutlets; t_signal **iosigs; float srate; int vecsize; /* vector size, power of two */ int calcsize; /* number of elements to calculate */ char toplevel; /* true if "iosigs" is invalid. */ char reblock; /* true if we have to reblock inlets/outlets */ char switched; /* true if we're switched */ }; static int ugen_sortno = 0; static t_dspcontext *ugen_currentcontext; void ugen_stop() { if (dsp_chain) { free(dsp_chain); dsp_chain = 0; } signal_cleanup(); } void ugen_start() { ugen_stop(); ugen_sortno++; dsp_chain = (t_int *)getbytes(sizeof(*dsp_chain)); dsp_chain[0] = (t_int)dsp_done; dsp_chainsize = 1; if (ugen_currentcontext) bug("ugen_start"); } int ugen_getsortno() {return ugen_sortno;} #if 0 void glob_foo(void *dummy, t_symbol *s, int argc, t_atom *argv) { int i, count; t_signal *sig; for (count = 0, sig = signal_usedlist; sig; count++, sig = sig->nextused) {} post("used signals %d", count); for (i = 0; i < MAXLOGSIG; i++) { for (count = 0, sig = signal_freelist[i]; sig; count++, sig = sig->nextfree) {} if (count) post("size %d: free %d", (1 << i), count); } for (count = 0, sig = signal_freeborrowed; sig; count++, sig = sig->nextfree) {} post("free borrowed %d", count); ugen_loud = argc; } #endif /* start building the graph for a canvas */ extern "C" t_dspcontext *ugen_start_graph(int toplevel, t_signal **sp, int ninlets, int noutlets) { t_dspcontext *dc = (t_dspcontext *)getbytes(sizeof(*dc)); if (ugen_loud) post("ugen_start_graph..."); dc->ugenlist = 0; dc->toplevel = toplevel; dc->iosigs = sp; dc->ninlets = ninlets; dc->noutlets = noutlets; dc->parentcontext = ugen_currentcontext; ugen_currentcontext = dc; return dc; } /* first the canvas calls this to create all the boxes... */ extern "C" void ugen_add(t_dspcontext *dc, t_object *obj) { t_ugenbox *x = (t_ugenbox *)getbytes(sizeof *x); int i; t_sigoutlet *uout; t_siginlet *uin; x->next = dc->ugenlist; dc->ugenlist = x; x->obj = obj; x->nin = obj_nsiginlets(obj); x->in = (t_siginlet *)getbytes(x->nin * sizeof (*x->in)); for (uin = x->in, i = x->nin; i--; uin++) uin->nconnect = 0; x->nout = obj_nsigoutlets(obj); x->out = (t_sigoutlet *)getbytes(x->nout * sizeof (*x->out)); for (uout = x->out, i = x->nout; i--; uout++) uout->connections = 0, uout->nconnect = 0; } /* and then this to make all the connections. */ extern "C" void ugen_connect(t_dspcontext *dc, t_object *x1, int outno, t_object *x2, int inno) { t_ugenbox *u1, *u2; int sigoutno = obj_sigoutletindex(x1, outno); int siginno = obj_siginletindex(x2, inno); if (ugen_loud) post("%s -> %s: %d->%d", class_getname(x1->ob_pd), class_getname(x2->ob_pd), outno, inno); for (u1 = dc->ugenlist; u1 && u1->obj != x1; u1 = u1->next) {} for (u2 = dc->ugenlist; u2 && u2->obj != x2; u2 = u2->next) {} if (!u1 || !u2 || siginno < 0) { pd_error(u1->obj, "signal outlet connect to nonsignal inlet (ignored)"); return; } if (sigoutno < 0 || sigoutno >= u1->nout || siginno >= u2->nin) { bug("ugen_connect %s %s %d %d (%d %d)", class_getname(x1->ob_pd), class_getname(x2->ob_pd), sigoutno, siginno, u1->nout, u2->nin); } t_sigoutlet *uout = u1->out + sigoutno; t_siginlet * uin = u2->in + siginno; /* add a new connection to the outlet's list */ t_sigoutconnect *oc = (t_sigoutconnect *)getbytes(sizeof *oc); oc->next = uout->connections; uout->connections = oc; oc->who = u2; oc->inno = siginno; /* update inlet and outlet counts */ uout->nconnect++; uin->nconnect++; } /* get the index of a ugenbox or -1 if it's not on the list */ static int ugen_index(t_dspcontext *dc, t_ugenbox *x) { int ret=0; for (t_ugenbox *u = dc->ugenlist; u; u = u->next, ret++) if (u == x) return ret; return -1; } /* put a ugenbox on the chain, recursively putting any others on that this one might uncover. */ static void ugen_doit(t_dspcontext *dc, t_ugenbox *u) { t_sigoutlet *uout; t_siginlet *uin; t_sigoutconnect *oc; t_class *klass = pd_class(u->obj); int i, n; /* suppress creating new signals for the outputs of signal-inlets and subpatches, except in the case we're an inlet and "blocking" is set. We don't yet know if a subcanvas will be "blocking" so there we delay new signal creation, which will be handled by calling signal_setborrowed in the ugen_done_graph routine below. When we encounter a subcanvas or a signal outlet, suppress freeing the input signals as they may be "borrowed" for the parent or subpatch; same exception as above, but also if we're "switched" we have to do a copy rather than a borrow. */ int nonewsigs = klass==canvas_class || (klass==vinlet_class && ! dc->reblock ); int nofreesigs = klass==canvas_class || (klass==voutlet_class && !(dc->reblock || dc->switched)); t_signal **insig, **outsig, **sig, *s1, *s2, *s3; t_ugenbox *u2; if (ugen_loud) post("doit %s %d %d", class_getname(klass), nofreesigs, nonewsigs); for (i = 0, uin = u->in; i < u->nin; i++, uin++) { if (!uin->nconnect) { t_sample *scalar; s3 = signal_new(dc->vecsize, dc->srate); /* post("%s: unconnected signal inlet set to zero", class_getname(u->obj->ob_pd)); */ if ((scalar = obj_findsignalscalar(u->obj, i))) dsp_add_scalarcopy(scalar, s3->v, s3->n); else dsp_add_zero(s3->v, s3->n); uin->signal = s3; s3->refcount = 1; } } insig = (t_signal **)getbytes((u->nin + u->nout) * sizeof(t_signal *)); outsig = insig + u->nin; for (sig = insig, uin = u->in, i = u->nin; i--; sig++, uin++) { int newrefcount; *sig = uin->signal; newrefcount = --(*sig)->refcount; /* if the reference count went to zero, we free the signal now, unless it's a subcanvas or outlet; these might keep the signal around to send to objects connected to them. In this case we increment the reference count; the corresponding decrement is in sig_makereusable(). */ if (nofreesigs) (*sig)->refcount++; else if (!newrefcount) signal_makereusable(*sig); } for (sig = outsig, uout = u->out, i = u->nout; i--; sig++, uout++) { /* similarly, for outlets of subcanvases we delay creating them; instead we create "borrowed" ones so that the refcount is known. The subcanvas replaces the fake signal with one showing where the output data actually is, to avoid having to copy it. For any other object, we just allocate a new output vector; since we've already freed the inputs the objects might get called "in place." */ *sig = uout->signal = nonewsigs ? signal_new(0, dc->srate) : signal_new(dc->vecsize, dc->srate); (*sig)->refcount = uout->nconnect; } /* now call the DSP scheduling routine for the ugen. This routine must fill in "borrowed" signal outputs in case it's either a subcanvas or a signal inlet. */ mess1(u->obj, gensym("dsp"), insig); /* if any output signals aren't connected to anyone, free them now; otherwise they'll either get freed when the reference count goes back to zero, or even later as explained above. */ for (sig = outsig, uout = u->out, i = u->nout; i--; sig++, uout++) { if (!(*sig)->refcount) signal_makereusable(*sig); } if (ugen_loud) { if (u->nin+u->nout==0) post("put %s %d", class_getname(u->obj->ob_pd),ugen_index(dc,u)); else if (u->nin+u->nout==1) post("put %s %d (%lx)", class_getname(u->obj->ob_pd),ugen_index(dc,u),long(sig[0])); else if (u->nin+u->nout==2) post("put %s %d (%lx %lx)",class_getname(u->obj->ob_pd),ugen_index(dc,u),long(sig[0]),long(sig[1])); else post("put %s %d (%lx %lx %lx ...)",class_getname(u->obj->ob_pd),ugen_index(dc,u),long(sig[0]),long(sig[1]),long(sig[2])); } /* pass it on and trip anyone whose last inlet was filled */ for (uout = u->out, i = u->nout; i--; uout++) { s1 = uout->signal; for (oc = uout->connections; oc; oc = oc->next) { u2 = oc->who; uin = &u2->in[oc->inno]; /* if there's already someone here, sum the two */ s2 = uin->signal; if (s2) { s1->refcount--; s2->refcount--; if (!signal_compatible(s1, s2)) {pd_error(u->obj, "%s: incompatible signal inputs", class_getname(u->obj->ob_pd)); return;} s3 = signal_newlike(s1); dsp_add_plus(s1->v, s2->v, s3->v, s1->n); uin->signal = s3; s3->refcount = 1; if (!s1->refcount) signal_makereusable(s1); if (!s2->refcount) signal_makereusable(s2); } else uin->signal = s1; uin->ngot++; if (uin->ngot < uin->nconnect) goto notyet; if (u2->nin > 1) for (uin = u2->in, n = u2->nin; n--; uin++) if (uin->ngot < uin->nconnect) goto notyet; ugen_doit(dc, u2); notyet: ; } } free(insig); u->done = 1; } /* once the DSP graph is built, we call this routine to sort it. This routine also deletes the graph; later we might want to leave the graph around, in case the user is editing the DSP network, to save having to recreate it all the time. But not today. */ extern "C" void ugen_done_graph(t_dspcontext *dc) { t_sigoutlet *uout; t_siginlet *uin; int i, n; t_block *blk; int period, frequency, phase, vecsize, calcsize; float srate; int reblock = 0, switched; int downsample = 1, upsample = 1; /* IOhannes */ /* debugging printout */ if (ugen_loud) { post("ugen_done_graph..."); for (t_ugenbox *u = dc->ugenlist; u; u = u->next) { post("ugen: %s", class_getname(u->obj->ob_pd)); for (uout = u->out, i = 0; i < u->nout; uout++, i++) for (t_sigoutconnect *oc = uout->connections; oc; oc = oc->next) { post("... out %d to %s, index %d, inlet %d", i, class_getname(oc->who->obj->ob_pd), ugen_index(dc, oc->who), oc->inno); } } } /* search for an object of class "block~" */ blk = 0; for (t_ugenbox *u = dc->ugenlist; u; u = u->next) { t_pd *zz = u->obj; if (pd_class(zz) == block_class) { if (blk) pd_error(blk, "conflicting block~ objects in same page"); else blk = (t_block *)zz; } } t_dspcontext *parent_context = dc->parentcontext; float parent_srate = parent_context ? parent_context->srate : sys_getsr(); int parent_vecsize = parent_context ? parent_context->vecsize : sys_getblksize(); if (blk) { int realoverlap; vecsize = blk->vecsize; if (!vecsize) vecsize = parent_vecsize; calcsize = blk->calcsize;if (!calcsize) calcsize = vecsize; realoverlap = blk->overlap; if (realoverlap > vecsize) realoverlap = vecsize; /* IOhannes { */ downsample = blk->downsample; upsample = blk->upsample; if (downsample > parent_vecsize) downsample=parent_vecsize; period = (vecsize * downsample) / (parent_vecsize * realoverlap * upsample); frequency = (parent_vecsize * realoverlap * upsample) / (vecsize * downsample); /* } IOhannes*/ phase = blk->phase; srate = parent_srate * realoverlap * upsample / downsample; /* IOhannes */ if (period < 1) period = 1; if (frequency < 1) frequency = 1; blk->frequency = frequency; blk->period = period; blk->phase = dsp_phase & (period - 1); if (!parent_context || realoverlap!=1 || vecsize!=parent_vecsize || downsample!=1 || upsample!=1) /* IOhannes */ reblock = 1; switched = blk->switched; } else { srate = parent_srate; vecsize = parent_vecsize; calcsize = parent_context ? parent_context->calcsize : vecsize; downsample = upsample = 1;/* IOhannes */ period = frequency = 1; phase = 0; if (!parent_context) reblock = 1; switched = 0; } dc->reblock = reblock; dc->switched = switched; dc->srate = srate; dc->vecsize = vecsize; dc->calcsize = calcsize; /* if we're reblocking or switched, we now have to create output signals to fill in for the "borrowed" ones we have now. This is also possibly true even if we're not blocked/switched, in the case that there was a signal loop. But we don't know this yet. */ if (dc->iosigs && (switched || reblock)) { t_signal **sigp; for (i = 0, sigp = dc->iosigs + dc->ninlets; i < dc->noutlets; i++, sigp++) { if ((*sigp)->isborrowed && !(*sigp)->borrowedfrom) { signal_setborrowed(*sigp, signal_new(parent_vecsize, parent_srate)); (*sigp)->refcount++; if (ugen_loud) post("set %lx->%lx", long(*sigp), long((*sigp)->borrowedfrom)); } } } if (ugen_loud) post("reblock %d, switched %d", reblock, switched); /* schedule prologs for inlets and outlets. If the "reblock" flag is set, an inlet will put code on the DSP chain to copy its input into an internal buffer here, before any unit generators' DSP code gets scheduled. If we don't "reblock", inlets will need to get pointers to their corresponding inlets/outlets on the box we're inside, if any. Outlets will also need pointers, unless we're switched, in which case outlet epilog code will kick in. */ for (t_ugenbox *u = dc->ugenlist; u; u = u->next) { t_pd *zz = u->obj; t_signal **outsigs = dc->iosigs; if (outsigs) outsigs += dc->ninlets; if (pd_class(zz) == vinlet_class) vinlet_dspprolog((t_vinlet *)zz, dc->iosigs, vecsize, calcsize, dsp_phase, period, frequency, downsample, upsample, reblock, switched); else if (pd_class(zz) == voutlet_class) voutlet_dspprolog((t_voutlet *)zz, outsigs, vecsize, calcsize, dsp_phase, period, frequency, downsample, upsample, reblock, switched); } int chainblockbegin = dsp_chainsize; /* DSP chain onset before block prolog code */ if (blk && (reblock || switched)) { /* add the block DSP prolog */ dsp_add(block_prolog, 1, blk); blk->chainonset = dsp_chainsize - 1; } /* Initialize for sorting */ for (t_ugenbox *u = dc->ugenlist; u; u = u->next) { u->done = 0; for (uout = u->out, i = u->nout; i--; uout++) uout->nsent = 0; for (uin = u->in, i = u->nin; i--; uin++) uin->ngot = 0, uin->signal = 0; } /* Do the sort */ for (t_ugenbox *u = dc->ugenlist; u; u = u->next) { /* check that we have no connected signal inlets */ if (u->done) continue; for (uin = u->in, i = u->nin; i--; uin++) if (uin->nconnect) goto next; ugen_doit(dc, u); next: ; } /* check for a DSP loop, which is evidenced here by the presence of ugens not yet scheduled. */ for (t_ugenbox *u = dc->ugenlist; u; u = u->next) if (!u->done) { t_signal **sigp; pd_error(u->obj, "DSP loop detected (some tilde objects not scheduled)"); /* this might imply that we have unfilled "borrowed" outputs which we'd better fill in now. */ for (i = 0, sigp = dc->iosigs + dc->ninlets; i < dc->noutlets; i++, sigp++) { if ((*sigp)->isborrowed && !(*sigp)->borrowedfrom) { t_signal *s3 = signal_new(parent_vecsize, parent_srate); signal_setborrowed(*sigp, s3); (*sigp)->refcount++; dsp_add_zero(s3->v, s3->n); if (ugen_loud) post("oops, belatedly set %lx->%lx", long(*sigp), long((*sigp)->borrowedfrom)); } } break; /* don't need to keep looking. */ } /* add block DSP epilog */ if (blk && (reblock || switched)) dsp_add(block_epilog, 1, blk); int chainblockend = dsp_chainsize; /* and after block epilog code */ /* add epilogs for outlets. */ for (t_ugenbox *u = dc->ugenlist; u; u = u->next) { t_pd *zz = u->obj; if (pd_class(zz) == voutlet_class) { t_signal **iosigs = dc->iosigs; if (iosigs) iosigs += dc->ninlets; voutlet_dspepilog((t_voutlet *)zz, iosigs, vecsize, calcsize, dsp_phase, period, frequency, downsample, upsample, reblock, switched); } } int chainafterall = dsp_chainsize; /* and after signal outlet epilog */ if (blk) { blk->blocklength = chainblockend - chainblockbegin; blk->epiloglength = chainafterall - chainblockend; blk->reblock = reblock; } if (ugen_loud) { t_int *ip; if (!dc->parentcontext) for (i=dsp_chainsize, ip=dsp_chain; i--; ip++) post("chain %lx", *ip); post("... ugen_done_graph done."); } /* now delete everything. */ while (dc->ugenlist) { for (uout = dc->ugenlist->out, n = dc->ugenlist->nout; n--; uout++) { t_sigoutconnect *oc = uout->connections, *oc2; while (oc) { oc2 = oc->next; free(oc); oc = oc2; } } free(dc->ugenlist->out); free(dc->ugenlist->in); t_ugenbox *u = dc->ugenlist; dc->ugenlist = u->next; free(u); } if (ugen_currentcontext == dc) ugen_currentcontext = dc->parentcontext; else bug("ugen_currentcontext"); free(dc); } t_signal *ugen_getiosig(int index, int inout) { if (!ugen_currentcontext) bug("ugen_getiosig"); if (ugen_currentcontext->toplevel) return 0; if (inout) index += ugen_currentcontext->ninlets; return ugen_currentcontext->iosigs[index]; } /* resampling code originally by Johannes Zmölnig in 2001 */ /* also "block-resampling" added by Johannes in 2004.09 */ /* --------------------- up/down-sampling --------------------- */ /* LATER: add some downsampling-filters for HOLD and LINEAR */ /* up: upsampling factor */ /* down: downsampling factor */ /* parent: original vectorsize */ t_int *downsampling_perform_0(t_int *w) { PERFORM4ARGS(t_float *,in, t_float *,out, int,down, int,parent); int n=parent/down; while(n--) {*out++=*in; in+=down;} return w+5; } /* the downsampled vector is exactly the first part of the parent vector; the rest of the parent is just skipped * cool for FFT-data, where you only want to process the significant (1st) part of the vector */ t_int *downsampling_perform_block(t_int *w) { PERFORM4ARGS(t_float *,in, t_float *,out, int,down, int,parent); int n=parent/down; while(n--) *out++=*in++; return w+5; } t_int *upsampling_perform_0(t_int *w) { PERFORM4ARGS(t_float *,in, t_float *,out, int,up, int,parent); int n=parent*up; t_float *dummy = out; while(n--) *out++=0; n = parent; out = dummy; while(n--) {*out=*in++; out+=up;} return w+5; } t_int *upsampling_perform_hold(t_int *w) { PERFORM4ARGS(t_float *,in, t_float *,out, int,up, int,parent); int i=up; t_float *dum_out = out; t_float *dum_in = in; while (i--) { int n = parent; out = dum_out+i; in = dum_in; while(n--) {*out=*in++; out+=up;} } return w+5; } t_int *upsampling_perform_linear(t_int *w) { PERFORM5ARGS(t_resample *,x, t_float *,in, t_float *,out, int,up, int,parent); const int length = parent*up; t_float a=*x->buffer, b=*in; const t_float up_inv = (t_float)1.0/up; t_float findex = 0.f; for (int n=0; nbuffer = a; return w+6; } /* 1st part of the upsampled signal-vector will be the original one; 2nd part of the upsampled signal-vector is just 0 * cool for FFT-data, where you only want to process the significant (1st) part of the vector */ t_int *upsampling_perform_block(t_int *w) { PERFORM4ARGS(t_float *,in, t_float *,out, int,up, int,parent); int i=parent; int n=parent*(up-1); while (i--) *out++=*in++; while (n--) *out++=0.f; return w+5; } /* ----------------------- public -------------------------------- */ /* utils */ void resample_init(t_resample *x) { x->method=0; x->downsample=x->upsample=1; x->n = x->coefsize = x->bufsize = 0; x->v = x->coeffs = x->buffer = 0; } void resample_free(t_resample *x) { if (x->n) free(x->v); if (x->coefsize) free(x->coeffs); if (x->bufsize) free(x->buffer); x->n = x->coefsize = x->bufsize = 0; x->v = x->coeffs = x->buffer = 0; } void resample_dsp(t_resample *x, t_sample* in, int insize, t_sample* out, int outsize, int method) { if (insize == outsize) {bug("nothing to be done"); return;} if (insize > outsize) { /* downsampling */ if (insize % outsize) {error("bad downsampling factor"); return;} switch (method) { case RESAMPLE_BLOCK: dsp_add(downsampling_perform_block, 4, in, out, insize/outsize, insize); break; default: dsp_add(downsampling_perform_0, 4, in, out, insize/outsize, insize); } } else { /* upsampling */ if (outsize % insize) {error("bad upsampling factor"); return;} switch (method) { case RESAMPLE_HOLD: dsp_add(upsampling_perform_hold, 4, in, out, outsize/insize, insize); break; case RESAMPLE_LINEAR: if (x->bufsize != 1) { free(x->buffer); x->bufsize = 1; x->buffer = (t_float *)t_getbytes(x->bufsize*sizeof(*x->buffer)); } dsp_add(upsampling_perform_linear, 5, x, in, out, outsize/insize, insize); break; case RESAMPLE_BLOCK: dsp_add(upsampling_perform_block, 4, in, out, outsize/insize, insize); break; default: dsp_add(upsampling_perform_0, 4, in, out, outsize/insize, insize); } } } void resamplefrom_dsp(t_resample *x, t_sample *in, int insize, int outsize, int method) { if (insize==outsize) { free(x->v); x->n = 0; x->v = in; return;} if (x->n != outsize) { free(x->v); x->v = (t_float *)t_getbytes(outsize * sizeof(*x->v)); x->n = outsize;} resample_dsp(x, in, insize, x->v, x->n, method); } void resampleto_dsp(t_resample *x, t_sample *out, int insize, int outsize, int method) { if (insize==outsize) {if (x->n) free(x->v); x->n = 0; x->v = out; return;} if (x->n != insize) { free(x->v); x->v = (t_float *)t_getbytes(insize * sizeof(*x->v)); x->n = insize;} resample_dsp(x, x->v, x->n, out, outsize, method); } /* ------------------------ samplerate~ -------------------------- */ static t_class *samplerate_tilde_class; struct t_samplerate : t_object { t_canvas *canvas; }; extern "C" void *canvas_getblock(t_class *blockclass, t_canvas **canvasp); static void samplerate_tilde_bang(t_samplerate *x) { float srate = sys_getsr(); t_canvas *canvas = x->canvas; while (canvas) { t_block *b = (t_block *)canvas_getblock(block_class, &canvas); if (b) srate *= float(b->upsample) / float(b->downsample); } x->ob_outlet->send(srate); } static void *samplerate_tilde_new(t_symbol *s) { t_samplerate *x = (t_samplerate *)pd_new(samplerate_tilde_class); outlet_new(x,&s_float); x->canvas = canvas_getcurrent(); return x; } static void samplerate_tilde_setup() { samplerate_tilde_class = class_new2("samplerate~",samplerate_tilde_new,0,sizeof(t_samplerate),0,""); class_addbang(samplerate_tilde_class, samplerate_tilde_bang); } /* -------------------- setup routine -------------------------- */ void d_ugen_setup () { block_tilde_setup(); samplerate_tilde_setup(); }