#include <stdio.h>
#include <midi.h>

float Tempo = 200., atof();
int midi, Annoy=1;
int GlitchGap = 1;

Error(s){ MidiError("%s: ", av0), perror(s), exit(1); }

RandomNote()
/*
** Play a random note with random velocity for a random time:
** turn the note on, pause a bit, then turn it off.
*/
{
	int p;
#define rnd(a,b)    (random()%(b-a)+a)    /* return # in range a...b */

	NoteOn(midi, 0, p=rnd(dx7_MIN,dx7_MAX), velocity());
	fsleep((float)(rnd(10,100))/Tempo);
	NoteOff(midi, 0, p);
}

#d MAXdown 20
int Down[MAXdown]={0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0};
int Ndown=0;
#d Pitch(c) c[1]
#d Velocity(c) c[2]

EmptyDown(){
	Int i;
	loop(i,MAXdown) if (!Down[i]) return i;
	return MAXdown - 1;
}

PitchDown(p){
	Int i;
	loop(i,MAXdown) if (Down[i] == p) return i;
	return MAXdown - 1;
}

RandomDown(){
	Int i, limit=0;
	if (Ndown <= 0) return -1;
	do i = random()%MAXdown;
	while (!Down[i] && ++limit < 200);
	return Down[i];
}

next(n){ ++n; return n<MAXdown? n : 0; }
prev(n){ --n; return n>=0? n : MAXdown-1; }

UpSweep(){
	static int start = 0;
	Int i;
	i = start;
	do {
		if (Down[i]) return start = next(i), Down[i];
		i = next(i);
	} while (i != start);
	return -1;
}
DownSweep(){
	static int start = 0;
	Int i;
	i = start;
	do {
		if (Down[i]) return start = prev(i), Down[i];
		i = prev(i);
	} while (i != start);
	return -1;
}


int Select = 0;
PickDown(){
	switch (Select){
	case 1: return UpSweep();
	case 2: return DownSweep();
	}
	return RandomDown();
}

SetNote(c)
	unsigned char *c;
{
	if ((*c & M_CMD_MASK) != SX_CMD) *c &= M_CMD_MASK;
	if (*c == CH_KEY_ON) {
		if (Velocity(c)) {
			++Ndown; Down[EmptyDown()] = Pitch(c);
		} else {
			--Ndown; Down[PitchDown(Pitch(c))] = 0;
		}
	}
}

Flip(n) { return (random()%n) == n/2; }

int GlitchFreq = 2000;

int GlitchVel = 50;
int GlitchVelInc = 2;
int GlitchDur = 15;

velocity(){
	static int pv=10;
	if (GlitchVel == -2){
		if (pv > 99 || pv < 1) GlitchVelInc = -GlitchVelInc;
		return pv += GlitchVelInc;
	} else if (GlitchVel == -1) return rnd(20,99);
	else return GlitchVel;
}

duration(){
	if (GlitchDur>0) return GlitchDur;
	return rnd(4,100);
}

Glitch(){	/* play a glitch */
	int n;
	if (!Annoy) return;
	if (Ndown <= 0){ /* no notes down; play a random note, maybe */
		if (Flip(GlitchFreq)) RandomNote();
		return ;
	}
		/* pick a held note and play a glitch nearby */
	n = PickDown();
	if (n == -1) return;
	n = GlitchGap? rnd((n-GlitchGap), (n+GlitchGap)) : n;
	NoteOn(midi, 0, n, velocity());
	fsleep((float)duration()/Tempo);
	NoteOff(midi, 0, n);
}

#d sp(c) (c==' ' || c=='\t' || c == '\n')

DoCommand(f)
	FILE *f;
/*
** Read command from 'f' and do it.
*/
{
	char s[1024], *p = s;
	if (!fgets(s,sizeof s,f)) return;
	while sp(*p) ++p;
	switch (*p){
	Case 'e': GlitchGap = atoi(p+1);
	Case 'g': GlitchFreq = atoi(p+1);
	Case 'a': Annoy = (Annoy? 0 : 1);
	Case 't': Tempo = atof(p+1);
	Case 'v': GlitchVel = atoi(p+1);
	Case 'i': GlitchVelInc = atoi(p+1);
	Case 'd': GlitchDur = atoi(p+1);
	Case 's': Select = atoi(p+1);
	Case 'q': exit(0);
	}
}

main(ac, av) char *av[]; {
	Int i, n = 100;
	FILE *Midi, *f = stdin, *popen();
	MpuCmd *m = Alloc(MpuCmd);
	int F, fromstdin=0;

	srandom(42);	/* 42 is the most random number... */
	if ((midi = open(MidiDevice, 2)) == -1) Error(MidiDevice);

	for_each_argument{
	Case 'n': n = atoi(argument);      /* number of notes to play */
	Case 'r': srandom(atoi(argument)); /* use argument as seed */
	Case 'a': Annoy = 0;
	Case 't': Tempo = atof(argument);
	Case 'g': GlitchFreq = atoi(argument);
	Case 'e': GlitchGap = atoi(argument);
	Case 'v': GlitchVel = atoi(argument);
	Case 'i': GlitchVelInc = atoi(argument);
	Case 'd': GlitchDur = atoi(argument);
	Case 's': Select = atoi(argument);
	Case 'S': fromstdin = 1;
	Default : MidiError("use: %s [-n #notes] [-r seed] [-t tempo] [-a]\n",av0),
		  exit(1);
	}
#d mp(x) MpuSet(MPU_/**/x)
	mp(RESET), mp(BENDER_ON), mp(MIDI_THRU_OFF);
	if (Annoy) {
		mp(START_RECORD);
		if (!fromstdin) f = popen("marteauslide","r");
		Midi = fdopen(midi,"r+"); setbuf(Midi,NULL);
	}
	setbuf(f,0);
	F = fileno(f);
	MpuFlush(midi);

	if (Annoy) for (;;) {
		if (iwait(F,0)) DoCommand(f);
		while (iwait(midi,0) && GetMpuCmd(Midi,m)) SetNote(m->mpu_cmd);
		Glitch();
	} else {
		printf("Instant Boulez...\n");
		loop(i,n) RandomNote();
		printf("(Applause...)\n");
	}
	close(midi);
	dx7_reset(-1);
	exit(0);
}
