Csound Csound-dev Csound-tekno Search About

triginstr - new opcode

Date1999-09-02 23:50
Fromrasmus ekman
Subjecttriginstr - new opcode
(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