| (resend, didn't see this first time - hope it's not cluttering everybody's mailboxes...)
Greetings Csounders,
Here is an opcode which starts new instrument events from orchestra
at k-rate. Events are started every time a triggering signal is non-zero.
Event generation may be limited in two ways: by setting a minimum time
interval between generated events, or by specifying a maximum number
of simultaneous instruments instances. Events may also be deferred.
This opcode may be useful for things like generating strumming gestures,
or for some types of algorithmic composition.
Below are the code changes and two new files (triginstr.h and triginstr.c).
Documentation of the triginstr opcode follows at the end of this post.
Regards,
re
========================
CODE CHANGES
(A bit more to do than for other opcodes: Due to the way Csound handles
instrument event turnoff, this ugen requires some changes to core
stuff like OPARMS, playevents() and kperf() - we have to add a new type
of realtime event.)
In Entry.c, declare in the appropriate places near the top:
------------------------
#include "triginstr.h"
void triginset(TRIGINSTR *p), ktriginstr(TRIGINSTR *p);
------------------------
In Entry.c, anywhere in opcodlst[]:
------------------------
{ "triginstr", S(TRIGINSTR),3, "", "kkkkkz",triginset, ktriginstr, NULL },
========================
In Cs.h, in struct OPARMS, eg line 82 (new line marked >>>):
------------------------
int usingcscore, Linein, Midiin, FMidiin;
>>> int OrcEvts; /* - for triginstr (re Aug 1999) */
int RTevents, ksensing;
========================
Insert.c, among declares, eg line 31:
------------------------
int sensOrcEvent(void); /* For triginstr (re Aug 1999) */
------------------------
Insert.c, function kperf(), on lines 563 - 570:
Change the line marked ">>>", and insert the line marked "++>>":
------------------------
if (O.RTevents) {
if (O.Midiin && (sensType = sensMidi()) /* if MIDI note message */
|| O.FMidiin && kcounter >= FMidiNxtk
&& (sensType = sensFMidi())
>>> || O.Linein && (sensType = sensLine()) /* or Linein event */
++>> || O.OrcEvts && (sensType = sensOrcEvent())) /* or triginstr event (re Aug 1999) */
return(kreq - kcnt); /* do early return */
}
========================
Musmon.c, among header includes, eg line 6,
------------------------
#include "triginstr.h"
------------------------
Musmon.c, function playevents(), at label mtest, line 515:
Replace the lines
mtest:
if (sensType >= 2) { /* Midievent: */
with the following:
------------------------
mtest:
/* Old test: */
/* if (sensType >= 2) { /* Midievent: */
/* New version (re Aug 1999): */
if (sensType == 2 || sensType == 3) { /* Midievent: */
------------------------
NOTE: Somebody may feel queasy about changing the test here.
The present test captures all sensType values above 2. This is unnecessary,
as only 2 and 3 are ever used. This is quite easily verifiable: sensType
is set by calling sensMidi(), sensFMidi(), or sensLine(), as seen in the
code snippet from Insert.c above.
These functions only use direct return values 1 (sensLine), 2 (sensMidi)
or 3 (sensFMidi) - or zero for no event.
The dark horse is MacSensMidi(), which is called from sensMidi()
(#ifdef macintosh), but not provided with the Bath source distribution.
This function is however in the Mills Perf code distribution, and
returns 2 (or zero) as expected.
========================
Musmon.c, function playevents(), around line 470:
At the lines
if (sensType == 1) { /* for Linein, */
e = Linevtblk; /* get its evtblk */
e->p[2] = curp2; /* & insert curp2 */
}
>>> insert new lines here
if (!kdone) /* if null duration, */
goto mtest; /* chk for midi on-off */
Insert the following case (now the old lines are marked with >>>):
------------------------
>>> if (sensType == 1) { /* for Linein, */
>>> e = Linevtblk; /* get its evtblk */
>>> e->p[2] = curp2; /* & insert curp2 */
>>> }
else if(sensType == 4) { /* Realtime orc event (re Aug 1999) */
EVTNODE *evtlist = OrcTrigEvts.nxtevt;
while (evtlist && evtlist->kstart <= kcounter) {
int insno = evtlist->insno;
e = &evtlist->evt;
evtlist = OrcTrigEvts.nxtevt = evtlist->nxtevt;
/* If several events pending we must insert all but one
- code copied from switch(e->opcod) case 'i' below */
if(evtlist && evtlist->kstart <= kcounter) {
if (O.Beatmode && e->p3orig >= 0.)
e->p[3] = e->p3orig * betsiz;
if (n = insert(insno, e)) { /* alloc,init,activate */
printf("\t\t T%7.3f",curp2);
printf("triginstr note deleted. i%d had %d init errors\n",
insno, n);
perferrcnt++;
}
}
}
if(OrcTrigEvts.nxtevt == NULL) O.OrcEvts = 0;
}
>>> if (!kdone) /* if null duration, */
>>> goto mtest; /* chk for midi on-off */
========================
NEW FILES:
triginstr.h:
------------------------
/*****************************************************************/
/* triginstr - Start instrument events at k-rate from orchestra. */
/* August 1999 by rasmus ekman. */
/*****************************************************************/
typedef struct {
OPDS h;
FLOAT *trigger, *mintime, *maxinst;
FLOAT *args[PMAX+1];
FLOAT prvmintim;
long timrem, prvktim, kadjust;
} TRIGINSTR;
typedef struct eventnode {
EVTBLK evt; /* Must be first in struct so it can be typecast & freed */
struct eventnode *nxtevt;
int kstart, insno;
} EVTNODE;
extern EVTNODE OrcTrigEvts;
extern int sensOrcEvent(void);
========================
triginstr.c:
------------------------
/******************************************************************************/
/* triginstr - Ignite instrument events at k-rate from orchestra. */
/* August 1999 by rasmus ekman. */
/* Changes made also to Cs.h, Musmon.c and Insert.c; look for "(re Aug 1999)" */
/******************************************************************************/
#include "cs.h"
#include "triginstr.h"
/* Some global declarations we need */
extern INSTRTXT **instrtxtp; /* List of instrument declarations (orchestra) */
extern INSDS actanchor; /* Chain of active instrument instances */
extern void infoff(FLOAT p1); /* Turn off an indef copy of instr p1 */
extern long kcounter; /* Count of k-periods throughout performance */
extern int sensType; /* 0=score evt, 1=Linein, 2/3=midi, 4=triginstr */
#define FZERO (0.0f) /* (Shouldn't there be global decl's for these?) */
#define FONE (1.0f)
EVTNODE OrcTrigEvts; /* List of started events, used in playevents() (Musmon.c) */
/* Called from kperf() to see if any event to turn on */
int sensOrcEvent(void) {
if(OrcTrigEvts.nxtevt && OrcTrigEvts.nxtevt->kstart <= kcounter)
return(4); /* sensType value (0=score,1=line,2/3=midi) */
else return(0);
}
void ktriginstr(TRIGINSTR *p);
void triginset(TRIGINSTR *p) {
p->prvmintim = *p->mintime;
p->timrem = 0;
/* An instrument is initialised before kcounter is incremented for
this k-cycle, and begins playing after kcounter++.
Therefore, if we should start something at the very first k-cycle of
performance, we must thus do it now, lest it be one k-cycle late.
But in ktriginstr() we'll need to use kcounter-1 to set the start time
of new events. So add a separate variable for the kcounter offset (-1). */
if(kcounter == 0 && *p->trigger > FZERO && *p->args[1] <= FZERO) {
p->kadjust = 0; /* No kcounter offset at this time */
ktriginstr(p);
}
p->kadjust = -1; /* Set kcounter offset for perf-time */
return;
}
void ktriginstr(TRIGINSTR *p) { /* k-rate event generator */
int i, absinsno, argnum;
FLOAT insno, starttime;
EVTNODE *evtlist, *newnode;
EVTBLK *newevt;
if(*p->trigger == FZERO) /* Only do something if triggered */
return;
/* Get absolute instr num */
insno = *p->args[0];
absinsno = abs((int)insno);
/* Check that instrument is defined */
if(absinsno > maxinsno || instrtxtp[absinsno] == NULL) {
printf("triginstr ignored. Instrument %d undefined\n", absinsno);
perferrcnt++;
return;
}
/* On neg instrnum, turn off a held copy.
(Regardless of mintime/maxinst) */
if(insno < FZERO) {
infoff(-insno);
return;
}
/* Check if mintime has changed */
if(p->prvmintim != *p->mintime) {
long timrem = (int)(*p->mintime * ekr + 0.5f);
if(timrem > 0) {
/* Adjust countdown for new mintime */
p->timrem += timrem - p->prvktim;
p->prvktim = timrem;
} else timrem = 0;
p->prvmintim = *p->mintime;
}
/* Check for rate limit on event generation and count down */
if(*p->mintime > FZERO && --p->timrem > 0)
return;
/* See if there are too many instances already */
if(*p->maxinst >= FONE) {
INSDS *ip;
int numinst = 0;
/* Count active instr instances */
ip = &actanchor;
while ((ip = ip->nxtact) != NULL)
if(ip->insno == absinsno) numinst++;
if(numinst >= (int)*p->maxinst)
return;
}
/* Create the new event */
newnode = (EVTNODE *) mmalloc((long)sizeof(EVTNODE));
newevt = &newnode->evt;
newevt->opcod = 'i';
/* Set start time from kwhen */
starttime = *p->args[1];
if(starttime < FZERO) {
starttime = FZERO;
warning("triginstr warning: negative kwhen reset to zero");
}
/* Add current time (see note about kadjust in triginset() above) */
starttime += (FLOAT)(kcounter + p->kadjust) * onedkr;
newnode->kstart = (long)(starttime * ekr + 0.5f);
newevt->p2orig = starttime;
newevt->p3orig = *p->args[2];
/* Copy all arguments to the new event */
newevt->pcnt = argnum = p->INOCOUNT-3;
for(i = 0; i < argnum; i++)
newevt->p[i+1] = *p->args[i];
newevt->p[2] = starttime; /* Set actual start time in p2 */
newnode->insno = absinsno;
/* Set event activation time in k-cycles */
newnode->kstart = (long)(starttime * ekr + 0.5f);
/* Insert eventnode in list of generated events */
evtlist = &OrcTrigEvts;
while (evtlist->nxtevt) {
if(newnode->kstart < evtlist->nxtevt->kstart) break;
evtlist = evtlist->nxtevt;
}
newnode->nxtevt = evtlist->nxtevt;
evtlist->nxtevt = newnode;
O.RTevents = 1; /* Make sure kperf() looks for RT events */
O.ksensing = 1;
O.OrcEvts = 1; /* - of the appropriate type */
/* Reset min pause counter */
if(*p->mintime > FZERO)
p->timrem = (long)(*p->mintime * ekr + 0.5f);
else p->timrem = 0;
return;
}
========================
DOCUMENTATION:
------------------------
Ignite Instrument Events
triginstr ktrigger, kmintime, kmaxinst, kinstr, kwhen, kdur[, kp4, ... kpN]
Generate new instrument events from orchestra.
This opcode may be useful eg for certain kinds of algorithmic composition.
ARGUMENTS
ktrigger - when non-zero, signal that an instrument instance
should be started.
kmintime - controls the minimal time which is to elapse between
generated events, in seconds. If kmintime <= 0, no time limit is
used. If the kinstr instrument number is negative (to turn off
an instrument), this test is bypassed.
kmaxinst - ceiling for the number of simultaneous instances of
instrument kinstr. If number of existant instances of kinstr
is >= kmaxinst, no event is generated. If kmaxinst is <= 0, it is
not used to limit event generation. If the kinstr instrument
number is negative (to turn off an instrument), this test is
bypassed.
kinstr, kwhen, kdur, ... - same arguments as in score i statements.
The kwhen start time (p2) is measured from the time of the triggering
event, and must be a positive value (or zero).
If an event is deferred by a p2 offset value, the instrument will
not be initialized until the actual time when it should start performing.
PERFORMANCE
triginstr adds a new instrument event to the Csound performance
each time the krate value ktrigger is non-zero. Event generation
may be limited by the kmintime and kmaxinst arguments. The instrument
will be initialized at the time when it actually begins playing.
The arguments for the generated instrument event are the same as
in a score i-statement. The kwhen time (p2) is measured from the time
of the triggering event. If kdur (p3) is zero the instrument will
only do an initialization pass without performance. A negative kdur
value will initiate a held note (see also ihold and i-statements).
Note that while delayed events are pending, and no events are
playing, the performance must be kept going, or Csound will quit.
To guarantee continued performance, the f0 statement may be used
in score (same as eg when playing midi files, or padding score
sections with silence).
EXAMPLES
The offset value is useful for triggering multiple instruments
in fixed relations. Here, two alternating metronomes:
instr 5 ; tick-tock, tick-tock
iBPM init p4
; Instr's 6 and 7 could be eg simple sample players
triginstr 1, 60/iBPM, 0, 6, 0, .5
triginstr 1, 60/iBPM, 0, 7, 30/iBPM, .5
endin
;Score
; Generate events at 150 BPM for 10 secs
i1 0 10 150
This example will create a strumming gesture of p5 notes in p6 seconds
instr 10 ; Strummer
; Pass some values to the strummed instr
koct init p4
iamp init 12000
; Init strum delay so p5 events start in (roughly) p6 seconds
iNumEvts init p5
knum init 0
iDelBase init (p6 / sqrt(iNumEvts)) * 1.7
kdel init 0
; Generate p5 events in the first p5 k-cycles
knum = knum + 1
ktrig = (knum <= iNumEvts ? 1 : 0)
; Strum instr 11, passing pitch and "intended pitch" to pluck
; for some spectral variation. (knum is also passed for diagnostics)
triginstr ktrig, 0, 0, 11, kdel, .5, iamp, cpspch(koct), cpspch(koct) * (knum+2), knum
; Increase delay for next events
kdel = kdel + iDelBase / (knum+1)
; and play a chord (just to do something)
koct = koct +.03
; Don't need this instr after the evts generated, so turn off
if knum < iNumEvts kgoto continue
turnoff
continue:
endin
instr 11 ; Strummed instrument
; print p2, pchoct(octcps(p5)), p6, p7
kamp linseg p4, p3-.1, p4, .1, 0
ar pluck kamp, p5, p6, 0, 1
out ar
endin
; Score
i10 0 .1 6.10 6 .6
i10 1.5 .3 7.00 6 .7
i10 3 .3 6.03 11 1.5
f0 5
e
This example uses xyin to provide gestural control of event generation:
; Orchestra
sr = 22050
kr = 882
ksmps = 25
nchnls = 2
instr 9 ; Generate 1 - 50 events per sec (max 20 simultaneous)
kX, kY xyin .03, .02, 1, 0, 3000, 1, 100
triginstr 1, kX/2, 20, 2, 0, .7, kY, kY/(kX*.5)
endin
instr 12
kamp linseg 15000, p3-.05, 15000, .05, 0
kcps line p4, p3, p5
ar pluck kamp, p4, p5, 0, 1 pan
; Random stereo distribution
a1, a2 locsig ar, rnd(360), 1, 0
outs a1, a2
endin
; Score
f1 0 8192 10 1 ; Sine
i9 0 15 |