//
// Copyright(C) 1984-2000 David L. Clark
// Copyright(C) 2001-2005 Simon Howard
//
// You can redistribute and/or modify this program under the terms of the
// GNU General Public License version 2 as published by the Free Software
// Foundation, or any later version. This program is distributed WITHOUT
// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
// FITNESS FOR A PARTICULAR PURPOSE.
//
//
//        swsound  -      SW sound generation
//

#include <ctype.h>

#include "pcsound.h"
#include "timer.h"

#include "sw.h"
#include "swmain.h"
#include "swsound.h"

#define SNDSIZE 100

// These tables define the C major scale; this is the most common scale used in
// western music. The original music code in SOPWITH.EXE used the A natural
// minor scale where each octave begins begins at A. While this is a perfectly
// valid choice, it probably wasn't a deliberate one; it's more likely that the
// programmer who implemented the code didn't know that the usual C major scale
// begins at C.
// As a result of this change, the format used in SOPWITH.EXE is slightly
// different to the format we interpret here. To convert tunes between the two
// formats, you'll need to move all A and B notes up or down by one octave (add
// < or > commands)
static const int notefreq[] = {
	523,  // c
	554,  // c#
	587,  // d
	622,  // d#
	659,  // e
	698,  // f
	740,  // f#
	784,  // g
	831,  // g#
	880,  // a
	932,  // a#
	988,  // b
};

// Maps from a-g to index in notefreq[] above:
static const int noteindex[] = {
	9,  // a
	11, // b
	0,  // c
	2,  // d
	4,  // e
	5,  // f
	7   // g
};

static int soundtype = 32767;		/*  Current sound type and          */
static int soundparm = 32767;		/*     and priority parameter       */
static OBJECTS *soundobj = NULL;	/*  Object making sound             */
static unsigned lastfreq = 0;		/*  Last frequency used             */
static OBJECTS *lastobj = NULL;		/*  Previous object making sound    */
static void (*toneadj) () = NULL;	/*  Tone adjustment on clock tick   */

static TONETAB tonetab[SNDSIZE];	/*  Continuous tone table           */
static TONETAB *frsttone, *freetone;	/*  Tone list and free list         */
static unsigned soundticks;		/*  Ticks since last sound selection */

static int numexpls;			/*  Number of explosions currently  */
					/*  active                          */
static int explplace;			/*  Place in explosion tune;        */
static unsigned expltone;		/*  Current explosion tone          */
static int explticks;			/*  Ticks until note change         */
static int exploctv;			/*  Octave                          */

//#define SOPWITH1_TUNE

// Tunes found in Sopwith 1 and Sopwith 2. The Sopwith 1 tune is "Merrily We
// Roll Along" and is only found in that one version, although David Clark
// doesn't remember the game ever having this tune - a mystery!
// Note: the tune strings here have been changed from the original versions, to
// use C major scale as described above.
char *expltune =
#ifdef SOPWITH1_TUNE
	">e4./d8/c4/d4/e4/d+4/e4/c4/d4/d4/d4/d1/"
	"d4./c8/<b4/>c4/d4/c+4/d4/<b4/>c4/c4/c4/c1/<g4./g+8/"
	"a4./a-8/g4./g+8/a4/a-4/g4/>d4/d4/d2./<g4./g+8/"
	"a4./a-8/g4./g+8/a4/a-4/g4/>e4/e4/e2./"
	"e4./d8/c4/d4/e4/d+4/e4/c4/d4/d4/d4/d2/c4/<g+4/a4/>"
	"d2/e2/g1/";
#else
	"<b4/>d8/d2/r16/c8/<b8/a8/b4./>c4./c+4./d4./"
	"e4/g8/g2/r16/a8/g8/e8/d2./"
	"<b4/>d8/d2/r16/c8/<b8/a8/b4./>c4./c+4./d4./"
	"e4/a8/a2/r16/g8/f+8/e8/d2./"
	"d8/g2/r16/g8/g+2/r16/g+8/a2/r16/a8/>c2/<r16/"
	"b8/a8/g8/b4/g8/b4/g8/a4./g1/";
#endif

static int titlplace;		/*  Place in title tune;            */
static unsigned titltone;	/*  Current title tone              */
static int titlticks;		/*  Ticks until note change         */
static int titloctv;		/*  Octave                          */

static char *tune;		/* Tune player statics              */
static int place;
static unsigned tunefreq;
static int tunedura;
static int octavefactor;

// random number generator
static const int seed[50] = {
	0x90B9, 0xBCFB, 0x6564, 0x3313, 0x3190, 0xA980, 0xBCF0, 0x6F97,
	0x37F4, 0x064B, 0x9FD8, 0x595B, 0x1EEE, 0x820C, 0x4201, 0x651E,
	0x848E, 0x15D5, 0x1DE7, 0x1585, 0xA850, 0x213B, 0x3953, 0x1EB0,
	0x97A7, 0x35DD, 0xAF2F, 0x1629, 0xBE9B, 0x243F, 0x847D, 0x313A,
	0x3295, 0xBC11, 0x6E6D, 0x3398, 0xAD43, 0x51CE, 0x8F95, 0x507E,
	0x499E, 0x3BC1, 0x5243, 0x2017, 0x9510, 0x9865, 0x65F6, 0x6B56,
	0x36B9, 0x5026
};

static unsigned int swrand(unsigned int modulo)
{
	static int i = 0;
	int result;

	if (i >= 50) {
		i = 0;
	}

	result = seed[i] % modulo;
	++i;

	return result;
}

static TONETAB *allocton(void)
{
	TONETAB *tt;

	if (!freetone) {
		return 0;
	}

	tt = freetone;
	freetone = tt->tt_next;

	tt->tt_next = frsttone;
	tt->tt_prev = NULL;

	if (frsttone) {
		frsttone->tt_prev = tt;
	}

	frsttone = tt;

	return frsttone;
}

static void deallton(TONETAB * ttp)
{
	TONETAB *tt = ttp;
	TONETAB *ttb = tt->tt_prev;

	if (ttb) {
		ttb->tt_next = tt->tt_next;
	} else {
		frsttone = tt->tt_next;
	}

	ttb = tt->tt_next;

	if (ttb) {
		ttb->tt_prev = tt->tt_prev;
	}

	tt->tt_next = freetone;
	freetone = tt;
}

void initsndt(void)
{
	TONETAB *tt;
	int i;

	for (i = 0, tt = tonetab; i < (SNDSIZE - 1); ++i, ++tt) {
		tt->tt_next = tt + 1;
	}
	tt->tt_next = NULL;
	frsttone = NULL;
	freetone = tonetab;
}

void stopsound(OBJECTS * ob)
{
	TONETAB *tt = ob->ob_sound;

	if (!tt) {
		return;
	}

	if (ob->ob_type == EXPLOSION) {
		--numexpls;
	} else {
		deallton(tt);
	}
	ob->ob_sound = NULL;
}

static void soundoff(void)
{
	if (lastfreq) {

		Speaker_Off();

		lastfreq = 0;
	}
}

static void tone(unsigned int freq)
{
	if (!soundflg) {
		return;
	}

	if (lastfreq == freq) {
		return;
	}

	Speaker_Output(freq);

	lastfreq = freq;
}

// music note generation

#define NOTEEND     '/'
#define UPOCTAVE    '>'
#define DOWNOCTAVE  '<'
#define SHARP       '+'
#define FLAT        '-'
#define DOT         '.'
#define REST        'R'

static void playnote(void)
{
	static int durplace, test, freq, duration;
	static int index;
	static int indexadj;

	static char durstring[5];
	static char charatplace, noteletter;

	static int noteoctavefactor;
	static int dottednote;

	bool firstplace = true;

	indexadj = 0;
	durplace = 0;
	dottednote = 2;
	noteoctavefactor = 256;

	for (;;) {
		if (!place) {
			octavefactor = 256;
		}

		charatplace = toupper(tune[place]);
		++place;

		if (!charatplace) {
			place = 0;

			if (firstplace) {
				continue;
			}
			break;
		}
		firstplace = false;
		if (charatplace == NOTEEND) {
			break;
		}

		test = isalpha(charatplace);
		if (test) {
			index = *(noteindex + (charatplace - 'A'));
			noteletter = charatplace;
		} else {
			switch (charatplace) {
			case UPOCTAVE:
				octavefactor <<= 1;
				break;
			case DOWNOCTAVE:
				octavefactor >>= 1;
				break;
			case SHARP:
				indexadj++;
				break;
			case FLAT:
				indexadj--;
				break;
			case DOT:
				dottednote = 3;
				break;
			default:
				test = isdigit(charatplace);
				if (test) {
					durstring[durplace] = charatplace;
					++durplace;
				}
				break;
			}
		}
	}

	durstring[durplace] = '\0';
	duration = atoi(durstring);
	if (duration <= 0) {
		duration = 4;
	}
	duration = (1440 * dottednote / (60 * duration)) / 2;

	if (noteletter == REST) {
		tunefreq = 0;
	} else {
		index += indexadj;
		while (index < 0) {
			index += 12;
			noteoctavefactor /= 2;
		}
		while (index >= 12) {
			index -= 12;
			noteoctavefactor *= 2;
		}

		freq = notefreq[index];
		freq *= octavefactor;
		freq >>= 8;
		freq *= noteoctavefactor;
		freq >>= 8;
		tunefreq = 1331000 / freq;
	}
#ifdef SOPWITH1_TUNE
	tunedura = duration * 0.7;
#else
	tunedura = duration;
#endif
}

static void adjcont(void)
{
	TONETAB *tt = lastobj->ob_sound;

	if (tt) {
		tone(tt->tt_tone + tt->tt_chng * soundticks);
	}
}

static void adjshot(void)
{
	static unsigned savefreq;

	if (lastfreq == 0xF000) {
		tone(savefreq);
	} else {
		savefreq = lastfreq;
		tone(0xF000);
	}
}

static void explnote(void)
{
	place = explplace;
	tune = expltune;
	octavefactor = exploctv;
	playnote();
	explplace = place;
	expltone = tunefreq;
	explticks += tunedura;
	exploctv = octavefactor;
}

static void adjexpl(void)
{
	--explticks;

	if (explticks >= 0) {
		return;
	}

	explnote();
}

static void titlnote(void)
{
	place = titlplace;
	tune = expltune;
	octavefactor = titloctv;
	playnote();
	titlplace = place;
	titltone = tunefreq;
	titlticks += tunedura;
	titloctv = octavefactor;
	soundoff();
	tone(titltone);
}

static void adjtitl(void)
{
	--titlticks;

	if (titlticks >= 0) {
		return;
	}
	titlnote();
}

static void soundadj(void)
{

	++soundticks;

	if (lastfreq && toneadj) {
		(*toneadj) ();
	}

	if (numexpls) {
		adjexpl();
	}

	if (titleflg) {
		adjtitl();
	}
}

void swsound(void)
{
	TONETAB *tt;

	tt = frsttone;
	while (tt) {
		tt->tt_tone += (tt->tt_chng * soundticks);
		tt = tt->tt_next;
	}

	soundticks = 0;
	titleflg = false;

	switch (soundtype) {

	case 0:
	case 32767:
	default:
		soundoff();
		lastobj = NULL;
		toneadj = NULL;
		break;

	case S_PLANE:
		if (soundparm) {
			tone(0xF000 + soundparm * 0x1000);
		} else {
			tone(0xD000);
		}
		lastobj = NULL;
		toneadj = NULL;
		break;

	case S_BOMB:
		if (soundobj == lastobj) {
			break;
		}
		toneadj = adjcont;
		lastobj = soundobj;
		adjcont();
		break;

	case S_FALLING:
		if (soundobj == lastobj) {
			break;
		}
		toneadj = adjcont;
		lastobj = soundobj;
		adjcont();
		break;

	case S_HIT:
		tone(swrand(2) ? 0x9000 : 0xF000);
		lastobj = NULL;
		toneadj = NULL;
		break;

	case S_EXPLOSION:
		tone(expltone);
		toneadj = NULL;
		lastobj = NULL;
		break;

	case S_SHOT:
		tone(0x1000);
		toneadj = adjshot;
		lastobj = NULL;
		break;

	}

	soundtype = soundparm = 32767;
}

void sound(int type, int parm, OBJECTS * ob)
{
	// if we are already playing the title music, ignore
	if (type == S_TITLE) {
		if (!titleflg) {
			titlplace = 0;
			titlnote();
			toneadj = NULL;
			lastobj = NULL;
			titleflg = true;
		}
	} else if (type < soundtype) {
		soundtype = type;
		soundparm = parm;
		soundobj = ob;
	} else if (type == soundtype && parm < soundparm) {
		soundparm = parm;
		soundobj = ob;
	}
}

void initsound(OBJECTS *ob, int type)
{
	TONETAB *tt;

	if (ob->ob_sound) {
		return;
	}

	if (ob->ob_type == EXPLOSION) {
		if (++numexpls == 1) {
			explplace = 0;
			explnote();
		}
		ob->ob_sound = (struct tt *) 1;
		return;
	}

	if ((tt = allocton())) {
		switch (type) {
		case S_BOMB:
			tt->tt_tone = 0x0300;
			tt->tt_chng = 8;
			break;
		case S_FALLING:
			tt->tt_tone = 0x1200;
			tt->tt_chng = -8;
			break;
		default:
			break;
		}
		ob->ob_sound = tt;
		return;
	}
}

static int lastclock = 0;

void swsndupdate(void)
{
	int thisclock = Timer_GetMS();

	if (lastclock == 0) {
		lastclock = thisclock;
	}

	if (thisclock > lastclock + 1000 / 18.2) {
		lastclock += 1000 / 18.2;
		soundadj();
	}
}

//
// 2003-02-14: Code was checked into version control; no further entries
// will be added to this log.
//
// sdh 14/2/2003: change license header to GPL
// sdh 25/11/2001: remove intsoff, intson calls
// sdh 21/10/2001: rearranged headers, added cvs tags
// sdh 21/10/2001: reformatted with indent. adjusted some code by hand
//                 to make more readable
// sdh 20/10/2001: added #define to use sopwith 1 theme (extracted from
//                 hexdump of sopwith1.exe)
// sdh 19/10/2001: removed externs (now in headers)
//                 shuffled some functions round to shut up compiler
// sdh 18/10/2001: converted all functions to ANSI-style arguments
// sdh ??/10/2001: use SDL digitally emulated PC speaker
//
// 87-03-10        Microsoft compiler.
// 84-04-11        Development
//
