/*
 * Programm: AM (Algorithmische Musik)
 * Aufgabe : Nach Parametern zufällig MIDI-Events erzeugen, sync. durch
 *           Prg "Takt"
 * 
 * Version : 1.1
 * 
 * Aufruf  : AM [-w] [<file [>nil:]]
 *               -w = mit Parameterfenster
 *
 * Auto: make AM
 *
 */


#include "AM.h"
#include <exec/execbase.h>
#include <dos/dosextens.h>
#include <midi/kawai_k1.h>
#include <string.h>
#include <stdio.h>
//#include <stdlib.h>	und
//#include <math.h>		klappt nicht!


#if 0
# define D(debug) debug
#else
# define D(debug)
#endif

#define abbruch SIGBREAKF_CTRL_C


//------------------------- Daten anderer Module...


extern struct ExecBase	*SysBase;
extern struct Library	*DOSBase;


//------------------------- Code anderer Module...


extern BOOL startwindow( void ); // Subtask für GUI starten
extern void closewindow( void ); // Subtask stoppen


//------------------------- Daten dieses Moduls...


struct MidiBase		*MidiBase = NULL;
struct MSource		*src = NULL;
struct MRoute		*outroute = NULL;

struct MRouteInfo outinfo = {
	/* SysExs und alle ChannelMsgs passieren diese Route */
	MMF_SYSEX+MMF_CHAN,		// MsgFlags; s. midi.h
	-1,						// ChanFlags; -1 = alle
	0,						// ChanOffset; Kanalverschiebung der Route
	0,						// NoteOffset; Tonhöhenversch.    "    "
	{ 0,0,0,0 },			// SysExMatch; SysEx-Filter
	{ 0,0,0,0 } };			// CtrlMatch; Controller-Filter


struct MsgPort		*taktport = NULL;
struct TaktMsg		taktmsg;
long				taktsig=0, taktmask=0;


long				channel = 0,					// 0..15
					instr = -1,						// -1 = keine Änderung
					freqmitte = 63, freqabw = 48,	// mitte ± abw
					volmitte = 63, volabw = 48;		// mitte ± abw
double				pausen = 0.40,					// Pausenhäufigkeit
					tonaus = 0.30,					// NoteOff-Wahrsch.
					volblend = 0.1;					// Ein-/Ausblenden
char				skala[13] = "999999999999",		// Standard = Chromatisch
					cskala[12][13];					// Standard = Leer


BOOL				mitFenster = FALSE;			// Flag für Parameter-Window


//------------------------- Code dieses Moduls...


void init( void );
void closedown( void );
void nextton( void );


LONG Printf( UBYTE *format, ... )
{
	return( VPrintf(format, (LONG *)((long)&format + 4L)) );
}


main( int argc, char *argv[] )
{
	long sgns;
	
	if( argc > 1 )
	{
		if( argv[1][0]=='-' && argv[1][1]=='w' )
			mitFenster = TRUE;
	}
	init();
	
	while(1)
	{
	
		sgns = Wait( abbruch | taktmask );
		
		if( sgns & taktmask )
		{
			D(PutStr("TAKT!\n"));
			
			nextton();
			SetSignal( 0, taktmask );
		}
			
		if( sgns & abbruch )
		{
			closedown();
		}
		
	}
}


void dump_param( void )
{
	char buf[8];
	long i;
	void ftoa( double val, char *buf, int prec, int type );

	Printf( "Kanal=%ld\n",		channel );
	Printf( "Instr=%ld\n",		instr );
	Printf( "FreqMitte=%ld\n",	freqmitte );
	Printf( "FreqAbw=%ld\n",	freqabw );
	Printf( "VolMitte=%ld\n", 	volmitte );
	Printf( "VolAbw=%ld\n", 	volabw );
	ftoa( pausen, buf, 2, 1 );
	Printf( "Pausen=%s\n", buf );
	ftoa( tonaus, buf, 2, 1 );
	Printf( "TonAus=%s\n", buf );
	Printf( "Skala=%s\n", skala );
	
	for( i=0; i<=11; i++ )
	{
		if( cskala[i][0] )
			Printf( "CSkala=%02ld:%s\n", i, cskala[i] );
	}
	
	Printf( "Ende\n", NULL );
}


BOOL parse_param( char *par )
{
	static char key[64];
	char *ks = key;
	double atof( char * );
	long atol( char * );
	
	// Keyword parsen
	while( *par != '\0' && *par != '=' )
		*ks++ = *par++;
	*ks = '\0';
	// par hinter '=' positionieren
	par++;
	
	if(      stricmp(key,"KANAL")==0 )
		channel = atol(par);
	else if( stricmp(key,"INSTR")==0 )
		instr = atol(par);
	else if( stricmp(key,"FREQMITTE")==0 )
		freqmitte = atol(par);
	else if( stricmp(key,"FREQABW")==0 )
		freqabw = atol(par);
	else if( stricmp(key,"VOLMITTE")==0 )
		volmitte = atol(par);
	else if( stricmp(key,"VOLABW")==0 )
		volabw = atol(par);
	else if( stricmp(key,"PAUSEN")==0 )
		pausen = atof(par);
	else if( stricmp(key,"TONAUS")==0 )
		tonaus = atof(par);
	else if( stricmp(key,"SKALA")==0 )
		strncpy( skala, par, 12 );
	else if( stricmp(key,"CSKALA")==0 )
		strncpy( cskala[atol(par)], par+3, 12 );
	else
		return FALSE; // Unbekanntes Keyword -> Ende
	
	// OK
	return TRUE;
}


void read_param( void )
{
	char buf[512];

	memset( cskala, 0, sizeof(cskala) );
	D(PutStr( "Bitte Parameter eingeben:\n" ));
	
	while( gets(buf) )
		if( !parse_param(buf) )
			return;					// Eingabe-Ende durch unbekanntes Keyword
}


void init( void )
{
	struct MsgPort *p;
	char buf[16];
	void sran( double );
	void ProgChange( int );
	
	D(PutStr("init\n"));
	
	// Parameter einlesen
	read_param();
	
	// die Midi.library brauchen wir
	MidiBase = OpenLibrary( MIDINAME, MIDIVERSION );
	if( !MidiBase ) {
		PutStr("Benötigt midi.library!\n");
		closedown(); }
	
	// eine Midi-Datenquelle kreieren
	sprintf( buf, "AMSource%03ld",
		((struct Process *)SysBase->ThisTask)->pr_TaskNum );
	src = CreateMSource( buf, NULL );
	
	// und diese auf MidiOut routen
	outroute = MRouteSource(src, "MidiOut", &outinfo);
	
	taktport = CreateMsgPort();
	taktmsg.tm_msg.mn_ReplyPort = taktport;
	taktmsg.tm_msg.mn_Length = sizeof taktmsg;
	
	taktsig  = AllocSignal(-1);
	taktmask = 1L << taktsig;
	
	taktmsg.tm_op   = OP_ADD;
	taktmsg.tm_task = SysBase->ThisTask;
	taktmsg.tm_sig  = taktmask;
	
	Forbid();
	 if( p = FindPort("TAKT") )
	  PutMsg( p, &taktmsg );
	Permit();
	
	if( p )
	{
		D(PutStr("OP_ADD abgeschickt\n"));
		WaitPort( taktport );
		GetMsg( taktport );
		if( taktmsg.tm_task )
		{
			PutStr("Kein Taktslot mehr frei!\n");
			closedown();
		}
	}
	else
	{
		PutStr("Takt läuft nicht!\n");
		closedown();
	}
	D(PutStr("OP_ADD ok\n"));
	
	sran( (double) clock() );
	if( instr >= 0 ) ProgChange( instr );

	// GUI starten (goodie - egal ob ok)
	if( mitFenster )
	{
		D(PutStr("startwindow...\n"));
		startwindow();
	}
}


void closedown( void )
{
	struct MsgPort *p;
	void AllNotesOff( void );

	D(PutStr("closedown\n"));
	closewindow();
	
	AllNotesOff();
    if(outroute) DeleteMRoute(outroute);
    if(src) DeleteMSource(src);
    if(MidiBase) CloseLibrary(MidiBase);
	
	taktmsg.tm_op   = OP_REM;
	taktmsg.tm_task = SysBase->ThisTask;
	
	Forbid();
	 if( p = FindPort("TAKT") )
	  PutMsg( p, &taktmsg );
	Permit();
	
	if( p )
	{
		WaitPort( taktport );
		GetMsg( taktport );
	}
	
	FreeSignal( taktsig );
	DeleteMsgPort( taktport );
	
	// die (evtl. geänderten) Parameter ausgeben
	dump_param();
	
	exit(0);
}


void NoteOn( int note, int vel )	// Ton einschalten
{
	UBYTE msg[3];

	msg[0] = MS_NOTEON | channel;
	msg[1] = note;
	msg[2] = vel;
	PutMidiMsg(src, msg);
}

void NoteOff( int note )			// Ton ausschalten */
{
	UBYTE msg[3];

	msg[0] = MS_NOTEOFF | channel;
	msg[1] = note;
	msg[2] = 64;
	PutMidiMsg(src, msg);
}

void AllNotesOff( void )			// Alle Töne ausschalten
{
	UBYTE msg[3];

	msg[0] = MS_MODE | channel;
	msg[1] = MM_ALLOFF;
	msg[2] = 0;
	PutMidiMsg(src, msg);
}

void ProgChange( int nr )			// Sound wechseln
{
	UBYTE msg[3];

	msg[0] = MS_PROG | channel;
	msg[1] = nr;
	msg[2] = 0;
	PutMidiMsg(src, msg);
}


void nextton( void )
{
	static int last_note = 0;
	int vel;
	char *akt_skala;
	double r, ran();
	// ran() ist aus unerfindlichen Gründen hier negativ
	
	// ...wenn hier kein Kommentar steht... oder manchmal... oder...
	
	// ach scheiss drauf
	r = fabs(ran());
	D(printf("%lf\n",r));
	
	if( r > pausen )
	{
		// neue Note...
		D(PutStr("nextnote\n"));
		NoteOff( last_note );
		
		// Nächste erlaubte Note suchen (Skala)
		if( *(akt_skala = cskala[ last_note % 12 ]) == 0 )
			akt_skala = skala;
		
		last_note = freqmitte + 2 * freqabw * (0.50 - fabs(ran()));
		while( akt_skala[ last_note % 12 ] == '0' ) ++last_note;
		
		// Lautstärkegewichtung entsprechend Skala
		vel = volmitte + 2 * volabw * (0.50 - fabs(ran()));
		vel *= ( akt_skala[ last_note % 12 ] - '0' ) / 9.0;
		
		// Einblendung:
		if( volblend < 1.0 )
		{
			vel *= volblend;
			volblend += 0.1;
		}
		
		D(printf("key=%d, vel=%d\n",last_note,vel));
		NoteOn( last_note, vel );
	}
	else
	{
		// Pause: Ton evtl. ausschalten?
		D(PutStr("pause\n"));
		
		if( fabs(ran()) < tonaus )
		{
			NoteOff( last_note );
		}
	}
}
