    /*

    Copyright (C) 1998 Stefan Westerfeld
                       stefan@space.twc.de

    This program is free software; you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation; either version 2 of the License, or
    (at your option) any later version.

    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with this program; if not, write to the Free Software
    Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.

    */

#include <assert.h>
#include <stdio.h>
#include <malloc.h>

struct DSP_struct;
typedef struct DSP_struct DSP;

typedef struct {
	/* data goes here */
	int m_Position;
	int m_Size;

	/* every time I write data to a buffer, m_ToBeRead is incremented
	   by the amount of data for every DSP connected to that buffer,
	   since that (target) DSP has to read that data before I can write.
	   I can only write at max. (m_Size-m_ToBeRead) items to that buffer
	*/
	int m_ToBeRead;
} Buffer;

typedef struct {
	Buffer *m_Buffer;
	int m_Position;

/* connection is either incoming */
	DSP *m_SourceDSP;			/* is there a way to write DSP *m_SourceDSP? */

/* or outgoing */
	DSP **m_DestDSP;
	int m_DestDSPCount;	/* amount of clients */
} Connection;

struct DSP_struct {
	char *m_Name;
	Connection *m_In,*m_Out;
	int  m_NofIn, m_NofOut;
	long m_NeedCycles,m_CanPerform;
	int  m_Busy;
	int  m_BusyHit;
};

/* allocates a buffer - currently will not allocate room for the data,
   but only for the structure
*/
void buffer_init(Connection *conn)
{
	conn->m_Buffer = (Buffer *)calloc(sizeof(Buffer),1);
	conn->m_Buffer->m_Size = 512;
}

/* SETUP: call this to initialize a dsp. You need to specify the number of
   input & output channels
*/
void dsp_init(DSP *dsp, char *name, int in, int out)
{
	int i;

	dsp->m_Name = name;
	dsp->m_NofIn = in;
	dsp->m_NofOut = out;
	dsp->m_In = (Connection *)calloc(sizeof(Connection), in);
	dsp->m_Out = (Connection *)calloc(sizeof(Connection), out);
	dsp->m_NeedCycles = 0;
	dsp->m_CanPerform = 0;
	dsp->m_Busy = 0;
	dsp->m_BusyHit = 0;
	/* The busy flag is set for DSPs that currently can't answer requests,
	   since they are requesting something themselves ;)

	   The busyhit flag is set when such a DSP is asked to fulfill an
       request anyway. This indicates a feedback loop.
	*/

	for(i=0;i<out;i++) buffer_init(&dsp->m_Out[i]);
}

/* SETUP: call this to connect two dsps */

void dsp_connect(DSP *source_dsp,int source_channel,
                  DSP *dest_dsp, int dest_channel)
{
	Connection *outc = &source_dsp->m_Out[source_channel];
	int count;

	dest_dsp->m_In[dest_channel].m_Buffer =
		source_dsp->m_Out[source_channel].m_Buffer;
	dest_dsp->m_In[dest_channel].m_SourceDSP = source_dsp;

	count = outc->m_DestDSPCount++;
	outc->m_DestDSP = realloc(outc->m_DestDSP,sizeof(DSP *)*(count+1));
	outc->m_DestDSP[count] = dest_dsp;
}

/* SETUP: call this to let some dsp preproduce an amount of samples
   (this is suitable for a delay dsp or something like that)
*/
void dsp_produce(DSP *dsp, long amount)
{
	int i;

	for(i=0;i<dsp->m_NofOut;i++)
	{
		dsp->m_Out[i].m_Buffer->m_Position += amount; 	/* creates output */
		dsp->m_Out[i].m_Buffer->m_ToBeRead +=
			amount * dsp->m_Out[i].m_DestDSPCount;
	}
}

/* This routine would now actually let the plugin calculate some data
   that means generate sinus waves, mix audio signals etc.

   The number of cycles is guaranteed to work without input underrun
   by the flow system.
*/

int dsp_calc(DSP *dsp, int cycles)
{
	int i,room;

	for(i=0;i<dsp->m_NofOut;i++)
	{
		room = dsp_out_room(dsp,i);
		if(room < cycles)
		{
			cycles = room;
			/*
			printf(" - reduced calculation to %d due to lack of space\n",cycles);
			*/
		}
	}

	if(cycles == 0) return(0);

	printf("DSP %s: calculation of %d cycles\n",dsp->m_Name,cycles);
	for(i=0;i<dsp->m_NofIn;i++)
	{
		/* otherwise input is "overconsumed" */
		assert(dsp_in_count(dsp,i) >= cycles);

		/* otherwise, there is something wrong with the ToBeRead setting */
		assert(dsp->m_In[i].m_Buffer->m_ToBeRead >= cycles);

		dsp->m_In[i].m_Position += cycles;				/* consumes input */
		dsp->m_In[i].m_Buffer->m_ToBeRead -= cycles;				/* consumes input */
	}

	dsp_produce(dsp,cycles);

	dsp->m_NeedCycles -= cycles;
	dsp->m_CanPerform -= cycles;
	return(cycles);
}

/* request a dsp to calculate a number of turns
   will return -1 if busy, count that has been done otherwise */

int dsp_request(DSP *dsp, long amount)
{
	int i,in,have_in,in_room,need_more,have_done,have_done_total = 0;

	if(dsp->m_Busy) { dsp->m_BusyHit++; return(-1); }

	dsp->m_Busy = 1;
	if(dsp->m_NeedCycles < amount)
	{
		dsp->m_NeedCycles = amount;
	}

	/*
	printf("DSP %s: request of %d cycles\n",dsp->m_Name,amount);
	*/
	do
	{
		/* assume we can satisfy the request */
		dsp->m_CanPerform = dsp->m_NeedCycles;

		/* then, check wether the input channels supply enough data to do so. */

		for(in=0;in<dsp->m_NofIn;in++)
		{
			have_in = dsp_in_count(dsp,in); 

			if(have_in < dsp->m_NeedCycles)
			{
				need_more = dsp->m_NeedCycles - have_in;
				dsp_request(dsp->m_In[in].m_SourceDSP,need_more);

				have_in = dsp_in_count(dsp,in);
	
				if(dsp->m_CanPerform > have_in) dsp->m_CanPerform = have_in;
			}
		}
		have_done = dsp_calc(dsp,dsp->m_CanPerform);
		have_done_total += have_done;

		/*
		if(dsp->m_BusyHit != 0) printf("got busyhit: %s\n",dsp->m_Name);
		*/

	} while(dsp->m_BusyHit && dsp->m_NeedCycles != dsp->m_CanPerform
			&& have_done);
	/* makes only sense to repeat if
		 - there was a busyhit which indicates we are in a feedback loop
		 - we should have done more than we have done
         - actually something could be calculated
     */
	dsp->m_Busy = 0;
	return(have_done);
}

/* counts the input samples that are available on a channel of a dsp */
int dsp_in_count(DSP *dsp, int in)
{
	return(dsp->m_In[in].m_Buffer->m_Position - dsp->m_In[in].m_Position);
}

int dsp_out_room(DSP *dsp, int out)
{
	int room;

	room = dsp->m_Out[out].m_Buffer->m_Size
			- dsp->m_Out[out].m_Buffer->m_ToBeRead;
	if(room < 0) room = 0;
	return(room);
}

/* checks the integrity of a given flow system */
int flow_check(DSP *dsp, long size)
{
	int i,in,out,okay = 1;

	for(i=0;i<size;i++)
	{
		for(out=0;out<dsp[i].m_NofOut;out++)
		{
			if(dsp[i].m_Out[out].m_Buffer == 0)
			{
				printf("ERROR: %s outport %d uninitialized\n",
													dsp[i].m_Name,out);
				okay = 0;
			}
		}
		for(in=0;in<dsp[i].m_NofIn;in++)
		{
			if(dsp[i].m_In[in].m_Buffer == 0)
			{
				printf("ERROR: %s inport %d unconnected\n",dsp[i].m_Name,in);
				okay = 0;
			}
			if(dsp[i].m_In[in].m_SourceDSP == 0)
			{
				printf("ERROR: %s inport %d sourcedsp unassigned?\n",
												dsp[i].m_Name,in);
				okay = 0;
			}
		}
	}
	return(okay);
}

void dsp_exec(DSP *dspnet, int netsize, int cycles)
{
	int *done = calloc(netsize,sizeof(int));
	int i,j,incomplete,clients;

	do {
		incomplete = 0;		/* assume we have calculated all cycles for all
								consumers, and later increment if some are
								still missing */
		for(i=0;i<netsize;i++)
		{
			clients = 0;
			for(j=0;j<dspnet[i].m_NofOut;j++)
				clients += dspnet[i].m_Out[j].m_DestDSPCount;

			if(clients == 0)
			{
				/* a module whose input is not comsumed from other modules
					is a "push delivery" style module, such as speakers,
					or writing audio to log file, etc. and has to get
					external requests from the scheduling system */

				if(done[i] != cycles)
					done[i] += dsp_request(&dspnet[i],cycles-done[i]);
				assert(done[i] <= cycles);
				if(done[i] != cycles) incomplete++;
				printf("*scheduler*\n");
			}
		}
	} while(incomplete);

	free(done);
}

/*************************************************************************

So that is a simulation of the flow system I'd like to have. It can handle
feedback delays down to 1. The idea is that a feedback loop with a small
delay should impact only the performance of the modules inside the loop,
not of other modules.

In an real implementation the buffers should contain the sample data in
form of ring buffers. There are several things not handled right now that
can occur in such a real system, that is

 - event channels
 - multirate processing
 - sanity check (when an unsolvable feedback loop [no delay] occurs this
   implementation will crash)

The algorithm to do the evaluation should be pretty fast - at least I
suppose it is.

When you do an request to a dsp, it basically does these things:

 - it checks if it can fullfill the request, because there is enough
   data in the input channels
 - when there is some data missing, it requests the connected dsp to
   supply the missing data
 - if the connected dsp is not able to supply more data, it calculates
   the things it can calculate

I hope it really does everything allright. If you like, test the
performance with various settings, look if everything is done correctly
and play with it.

Some tuning can still be done (inling functions, removing some checks
that are not really required).

**************************************************************************/

main()
{
	int netsize = 6;
	DSP dspnet[netsize];
	DSP *a = &dspnet[0],*b = &dspnet[1],*c = &dspnet[2],*d = &dspnet[3],
		*e = &dspnet[4],*f = &dspnet[5];

/* 	This sets up the example I described in the dspxxx.h prototyping as well

a
|
v
b<-+
|  |
v  |
c--+--+
|     |
v     v
d     e

a supplies data b needs
b supplies data c needs
c supplies data b needs
c supplies data d needs

*/

/* Setup the modules (you can use more if you like, and nicer names, and
   more channels. The syntax is

    dsp_init(<pointer to some dsp>,<name>,<input channels>,<output channels>)
*/

   
	dsp_init(a,"a",0,1);		/* produces sin wave or something like that */
	dsp_init(b,"b",2,1);		/* mixes wave [a] and output of the delay [c] */
	dsp_init(c,"c",1,1);		/* delays [b] */
	dsp_init(d,"d",1,0);		/* audio output, plays [c] to speaker */
	dsp_init(e,"e",1,1);		/* audio logfile, writes [c] to wavfile */
	dsp_init(f,"f",1,0);		/* volume meter, displays e's volume */

/* Syntax for connections:

	dsp_connect(<from dsp>,<from channel>,<to dsp>,<to channel>)
*/
	dsp_connect(a,0,b,0);
	dsp_connect(b,0,c,0);
	dsp_connect(c,0,b,1);
	dsp_connect(c,0,d,0);
	dsp_connect(c,0,e,0);
	dsp_connect(e,0,f,0);

	dsp_produce(c,12);

	assert(flow_check(dspnet,netsize));

	printf("==> First test:\n");
	dsp_exec(dspnet,netsize,32);

	printf("==> Second test:\n");
	dsp_exec(dspnet,netsize,128);
}

