/*	CROSSFADE.C	Cross fade loop generator for the
			SoundScape sampler.

	(c) 1987 	Todor Fay
*/
	
#include "exec/exec.h"
#include "exec/types.h"
#include "intuition/intuition.h"
#include "soundscape.h"
#include "sampler.h"

/*	Cross Fade Icon for Patch Panel: */

UWORD crossfadedata[] = {	/* 36 x 11 */ 
0,	0,	0,	
16383,	65535,	49152,	
511,	65528,	0,	
15887,	65287,	49152,	
16368,	24831,	49152,	
16383,	40959,	49152,	
16368,	24831,	49152,	
15887,	65287,	49152,	
511,	65528,	0,	
16383,	65535,	49152,	
0,	0,	0,	
65535,	65535,	61440,	
49152,	4,	12288,	
49152,	57544,	12288,	
49153,	61700,	28672,	
55792,	24812,	61440,	
65535,	40959,	61440,	
55792,	24812,	61440,	
49153,	61444,	28672,	
49152,	57544,	12288,	
49152,	4,	12288,	
65535,	65535,	61440,	
};

struct Image crossfadeimage = 
	{ 0,0,36,11,2,crossfadedata,3,0,0 };

/*	Edit window.  This has the gadgets for selecting 
	sampler channel and octave, and Doing and Undoing
	the cross fade.  These intuition data structures
	were generated with Power Windows.
*/

USHORT BorderVectors1[] = {0,0,107,0,107,10,0,10,0,0};
struct Border Border1 = {
	-2,-1,
	3,0,JAM1,
	5,
	BorderVectors1,
	NULL
};

struct IntuiText IText1 = {
	1,0,JAM2,
	33,1,
	NULL,
	"Undo",
	NULL
};

struct Gadget UndoGadget = {
	NULL,
	23,55,
	104,9,
	GADGHCOMP,
	RELVERIFY,
	BOOLGADGET,
	(APTR)&Border1,
	NULL,
	&IText1,
	0,
	NULL,
	8,
	NULL
};

UBYTE SIBuffer7[4] =
	"5";
struct StringInfo GadgetSI7 = {
	SIBuffer7,
	NULL,
	0,
	4,
	0,
	0,0,0,0,0,
	0,
	5,
	NULL
};

USHORT BorderVectors2[] = {0,0,35,0,35,9,0,9,0,0};
struct Border Border2 = {
	-2,-1,
	1,0,JAM1,
	5,
	BorderVectors2,
	NULL
};

struct IntuiText IText2 = {
	2,0,JAM2,
	-60,0,
	NULL,
	"Octave",
	NULL
};

struct Gadget OctaveGadget = {
	&UndoGadget,
	95,28,
	32,8,
	GADGHCOMP,
	LONGINT+RELVERIFY+STRINGCENTER,
	STRGADGET,
	(APTR)&Border2,
	NULL,
	&IText2,
	0,
	(APTR)&GadgetSI7,
	7,
	NULL
};

UBYTE SIBuffer6[4] =
	"1";
struct StringInfo GadgetSI6 = {
	SIBuffer6,
	NULL,
	0,
	4,
	0,
	0,0,0,0,0,
	0,
	1,
	NULL
};

USHORT BorderVectors3[] = {0,0,35,0,35,9,0,9,0,0};
struct Border Border3 = {
	-2,-1,
	1,0,JAM1,
	5,
	BorderVectors3,
	NULL
};

struct IntuiText IText3 = {
	2,0,JAM2,
	-67,0,
	NULL,
	"Channel",
	NULL
};

struct Gadget ChannelGadget = {
	&OctaveGadget,
	95,15,
	32,8,
	GADGHCOMP,
	LONGINT+RELVERIFY+STRINGCENTER,
	STRGADGET,
	(APTR)&Border3,
	NULL,
	&IText3,
	0,
	(APTR)&GadgetSI6,
	6,
	NULL
};

USHORT BorderVectors4[] = {0,0,107,0,107,10,0,10,0,0};
struct Border Border4 = {
	-2,-1,
	3,0,JAM1,
	5,
	BorderVectors4,
	NULL
};

struct IntuiText IText4 = {
	1,0,JAM2,
	12,1,
	NULL,
	"Cross Fade",
	NULL
};

struct Gadget CrossFadeGadget = {
	&ChannelGadget,
	23,41,
	104,9,
	GADGHCOMP,
	RELVERIFY,
	BOOLGADGET,
	(APTR)&Border4,
	NULL,
	&IText4,
	0,
	NULL,
	5,
	NULL
};

/* Gadget list */

struct NewWindow NewWindowStructure = {
	0,0,
	152,72,
	0,1,
	GADGETUP+CLOSEWINDOW+RAWKEY,
	WINDOWSIZING+WINDOWDRAG+WINDOWDEPTH+WINDOWCLOSE
	+ACTIVATE,
	&CrossFadeGadget,
	NULL,
	" X Fade ",
	NULL,
	NULL,
	30,20,
	152,72,
	WBENCHSCREEN
};

unsigned short thisport;
opencode(direction)

/*	There is nothing to be initialised.  However,
	it would be nice to connect the console
	keyboard to the sampler so the user can test
	the cross fade loops, so make an OpenLink call.
	FindMidiPort returns the port id, given the
	name of the port.  Actually, these two ports
	have ids that are hard coded into SoundScape.
	This is an example what you should do with 
	modules that are independantly loaded into 
	the system.
*/

unsigned char direction;

{
    OpenLink(FindMidiPort("console keyboard"),
	FindMidiPort("sampler"));
    return(1);
}

closecode(direction)

unsigned char direction;

{
    return(1);
}

clearsounddata(sounddata)

/*	This routine frees up the sample buffer belonging
	to the SoundData structure 'sounddata'.
*/

struct SoundData *sounddata;

{
    if (sounddata->data) 
	FreeMem(sounddata->data,sounddata->length);
    sounddata->data = 0;
    sounddata->length = 0;
    sounddata->loopstart = 0;
    sounddata->loopend = 0;
    sounddata->start = 0;
}

crossfade(sounddata)

/*	This, the cross fade routine, operates on a 
	SoundData structure.

	The cross fade is done on the region of the
	sample between the two loop points.  The
	sample prior to the loop start is gradually
	mixed in until it is in full force by the
	end of the loop.  

	This is very straight forward, the only problem
	encountered is a sample with a loop that 
	is longer than the part leading into the
	loop.  This is solved by having the cross fade
	section be shorter - the length of the lead
	in section.
*/
	
struct SoundData *sounddata;

{
    char *sample;		/* Points to sample */
    unsigned short loop;	/* Length of loop. */
    unsigned short crosslength;	/* Length of cross fade. */
    unsigned short start;	/* Start point. */
    long result;		/* Intermediate result. */
    unsigned short a,b;		/* Indexes into sample. */
    unsigned short j;		/* Loop index. */
    
    loop = sounddata->loopend - sounddata->loopstart;
    sample = &sounddata->data[sounddata->start];
    start = sounddata->loopstart - sounddata->start;

/*	If the loop is greater than the start segment, set
	the crossfade to cover the last part of the loop,
	equal to the starting segment.
*/

    if (loop > start) {
	crosslength = start;
	a = 0;
	b = loop;
    }

/*	Otherwise, set the crossfade to cover the entire
	loop.
*/

    else {
	crosslength = loop;
	a = start - loop;
	b = start;
    }
    for (j = 0; j < crosslength; j++ ) {
	result = (j * sample[a]);
	result += (crosslength - j) * sample[b];
	result = result / crosslength;
	sample[b] = result;
	a++;
	b++;
    }
}

void useredit()

/*	This routine is called whenever the user clicks
	twice on the cross fade icon.  A window is opened
	and four gadgets are presented.  Two string
	gadgets allow the user to select the octave and
	channel of the sample they'd like to work on.
	Two boolean gadgets are used to command the
	cross fade and undo operations.

	There are two SoundData structures.  OldSound
	is used to keep an old copy of the sample
	around for undoing.  CFSound is used to hold
	the sample that gets cross faded.

	RAWKEY events are passed on to the console
	keyboard with the OutConsole command.
	This is so the user can test the changed sound
	without having to click on another window to
	enable the Console Keyboard.
*/

{
    struct IntuiMessage *message;
    struct SoundData *oldsound, *cfsound;
    short code, class;
    struct Gadget *gadget;
    struct Window *window;
    short octave = 5;
    short channel = 0;
    oldsound = (struct SoundData *) 
	AllocMem(sizeof(*oldsound),MEMF_CLEAR);
    if (!oldsound) return;
    cfsound = (struct SoundData *) 
	AllocMem(sizeof(*cfsound),MEMF_CLEAR);
    if (!cfsound) {
	FreeMem(oldsound,sizeof(*oldsound));
	return;
    }
    window = (struct Window *) 
	OpenWindow(&NewWindowStructure);
    while (window) {
	while (!(message = (struct IntuiMessage *) 
	  GetMsg(window->UserPort)))
	    WaitPort(window->UserPort);
	class = message->Class;
	code = message->Code;
	gadget = (struct Gadget *) message->IAddress;
	ReplyMsg(message);
	if (class == CLOSEWINDOW) break;
	if (class == RAWKEY) OutConsole(code);
	if (class == GADGETUP) {
	    switch (gadget->GadgetID) {
		case 5 :

/*	The crossfade gadget.  Get the sample, using 
	GetSoundData, and put it in cfsound.  Clear
	the undo buffer (oldsound) and get the same
	sample loaded into it.  Call crossfade to alter
	cfsound, then install it in the sampler with
	a call to SetSoundData.
	Then, clear out cfsound.
*/
	
		    if (!(GetSoundData(channel,
		      octave,cfsound))) {
			clearsounddata(oldsound);
			if (GetSoundData(channel,
			  octave,oldsound)) {
			    oldsound->data = 0;
			}
			crossfade(cfsound);
			SetSoundData(channel,octave,cfsound);
			clearsounddata(cfsound);
		    }
		    break;
		case 6 :

/*	This gadget simply sets the channel of the sampler
	that we are looking at.  Also, clear the undo buffer.
*/

		    channel = (GadgetSI6.LongInt - 1) & 0x0F;
		    clearsounddata(oldsound);
		    break;
		case 7 :

/*	This gadget sets the octave of the sample we are
	looking at.  Clear the undo buffer.
*/

		    octave = GadgetSI7.LongInt;
		    if (octave < 0) octave = 0;
		    if (octave > 9) octave = 9;
		    clearsounddata(oldsound);
		    break;
		case 8 :

/*	This is the undo command.  Simply give the undo
	buffer to the sampler, then clear the undo buffer.
*/

		    if (oldsound->data) {
			SetSoundData(channel,octave,oldsound);
			clearsounddata(oldsound);
		    }
		    break;
	    }
	}
    }
    CloseWindow(window);
    clearsounddata(oldsound);
    clearsounddata(cfsound);
    FreeMem(oldsound,sizeof(*oldsound));
    FreeMem(cfsound,sizeof(*cfsound));
}

editcode(direction,command,buffer)

/*	This is the edit routine provided to SoundScape 
	in the AddMidiPort call.

	There is nothing in the way of data structures 
	that we would like any other modules to access,
	nor is there anything to save or load with
	environment loads and save.  So, the GETSTATE, 
	SETSTATE, SAVESTATE, and LOADSTATE commands are 
	all ignored and the sample's length is set to
	0.

	However, USEREDIT indicates the user has clicked 
	twice on the cross fade icon and would like to 
	work with it. In that case, call the routine 
	'useredit' which puts up a window and lets the
	user do that wild cross fade thing.
	
*/

char direction, command;
long *buffer;

{
    static char not_editing = 1;
    *buffer = 0;
    switch (command) {
	case USEREDIT :
	    if (not_editing) {
		not_editing = 0;
		useredit();
		not_editing = 1;
	    }
	    break;
    }
}
	    
/*	The main program is simple.  Just open SoundScape to
	get the address of the library vector, then close
	the library (since this is a module and usually is
	invoked by SoundScape when the first program opens
	SoundScape, if it keeps the library count up, there
	will be no way to close SoundScape.)
	Check the version number.  If it's below 2, this
	SoundScape Sampler module doesn't support having
	samples changed, so quit.
	Else, add the module's port and wait for SoundScape
	to shut it down.
*/

long SoundScapeBase;
long IntuitionBase;

main() {
    SoundScapeBase = OpenLibrary("soundscape.library",0);
    if (SoundScapeBase) {
	IntuitionBase = OpenLibrary("intuition.library",0);
	CloseLibrary(SoundScapeBase);
	if (Version() > 1) {
	    thisport = AddMidiPort(opencode,closecode,
		editcode,0,&crossfadeimage,0,-1,"cross fade");	
	    SetTaskPri(FindTask(0),-20);
	    while (MidiPort(thisport)) Delay(100);
	}
	CloseLibrary(IntuitionBase);
    }
}
