/* ScummVM - Graphic Adventure Engine
 *
 * ScummVM is the legal property of its developers, whose names
 * are too numerous to list here. Please refer to the COPYRIGHT
 * file distributed with this source distribution.
 *
 * 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., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
 *
 * $URL: https://scummvm.svn.sourceforge.net/svnroot/scummvm/scummvm/tags/release-1-2-1/engines/agi/sound_pcjr.cpp $
 * $Id: sound_pcjr.cpp 52741 2010-09-16 04:43:49Z eriktorbjorn $
 *
 */

/* Heavily based on code from NAGI
 *
 * COPYRIGHT AND PERMISSION NOTICE
 *
 * Copyright (c) 2001, 2001, 2002 Nick Sonneveld
 *
 * All rights reserved.
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, and/or sell copies of the Software, and to permit persons
 * to whom the Software is furnished to do so, provided that the above
 * copyright notice(s) and this permission notice appear in all copies of
 * the Software and that both the above copyright notice(s) and this
 * permission notice appear in supporting documentation.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
 * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
 * OF THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
 * HOLDERS INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL
 * INDIRECT OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING
 * FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
 * NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
 * WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 *
 * Except as contained in this notice, the name of a copyright holder
 * shall not be used in advertising or otherwise to promote the sale, use
 * or other dealings in this Software without prior written authorization
 *
 */

#include "agi/agi.h"
#include "agi/sound.h"
#include "agi/sound_pcjr.h"

namespace Agi {

// "fade out" or possibly "dissolve"
// v2.9xx
const int8 dissolveDataV2[] = {
	-2, -3, -2, -1,
	0x00, 0x00,
	0x01, 0x01, 0x01, 0x01,
	0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
	0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
	0x04, 0x04, 0x04, 0x04,
	0x05, 0x05, 0x05, 0x05,
	0x06, 0x06, 0x06, 0x06, 0x06,
	0x07, 0x07, 0x07, 0x07,
	0x08, 0x08, 0x08, 0x08,
	0x09, 0x09, 0x09, 0x09,
	0x0A, 0x0A, 0x0A, 0x0A,
	0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
	0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
	0x0D,
	-100
};

// v3
const int8 dissolveDataV3[] = {
	-2, -3, -2, -1,
	0x00, 0x00, 0x00, 0x00, 0x00,
	0x01, 0x01, 0x01, 0x01, 0x01, 0x01,
	0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02, 0x02,
	0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03, 0x03,
	0x04, 0x04, 0x04, 0x04, 0x04,
	0x05, 0x05, 0x05, 0x05, 0x05,
	0x06, 0x06, 0x06, 0x06, 0x06,
	0x07, 0x07, 0x07, 0x07,
	0x08, 0x08, 0x08, 0x08,
	0x09, 0x09, 0x09, 0x09,
	0x0A, 0x0A, 0x0A, 0x0A,
	0x0B, 0x0B, 0x0B, 0x0B, 0x0B, 0x0B,
	0x0C, 0x0C, 0x0C, 0x0C, 0x0C, 0x0C,
	0x0D,
	-100
};


SoundGenPCJr::SoundGenPCJr(AgiEngine *vm, Audio::Mixer *pMixer) : SoundGen(vm, pMixer) {
	_chanAllocated = 10240; // preallocate something which will most likely fit
	_chanData = (int16 *)malloc(_chanAllocated << 1);

	// Pick dissolve method
	//
	// 0 = no dissolve.. just play for as long as it's meant to be played
	// this was used in older v2.4 and under games i THINK
	// 1 = not used
	// 2 = v2.9+ games used a shorter dissolve
	// 3 (default) = v3 games used this dissolve pattern.. slightly longer
	if (_vm->getVersion() >= 0x3000)
		_dissolveMethod = 3;
	else if (_vm->getVersion() >= 0x2900)
		_dissolveMethod = 2;
	else
		_dissolveMethod = 0;

	_dissolveMethod = 3;

	memset(_channel, 0, sizeof(_channel));
	memset(_tchannel, 0, sizeof(_tchannel));

	_mixer->playStream(Audio::Mixer::kMusicSoundType, &_soundHandle, this, -1, Audio::Mixer::kMaxChannelVolume, 0, DisposeAfterUse::NO, true);
}

SoundGenPCJr::~SoundGenPCJr() {
	free(_chanData);

	_mixer->stopHandle(_soundHandle);
}

void SoundGenPCJr::play(int resnum) {
	PCjrSound *pcjrSound = (PCjrSound *)_vm->_game.sounds[resnum];

	for (int i = 0; i < CHAN_MAX; i++) {
		_channel[i].data = pcjrSound->getVoicePointer(i % 4);
		_channel[i].duration = 0;
		_channel[i].avail = 0xffff;
		_channel[i].dissolveCount = 0xFFFF;
		_channel[i].attenuation = 0;
		_channel[i].attenuationCopy = 0;

		_tchannel[i].avail = 1;
		_tchannel[i].noteCount = 0;
		_tchannel[i].freqCount = 250;
		_tchannel[i].freqCountPrev = -1;
		_tchannel[i].atten = 0xF;	// silence
		_tchannel[i].genType = kGenTone;
		_tchannel[i].genTypePrev = -1;
	}
}

void SoundGenPCJr::stop(void) {
	int i;

	for (i = 0; i < CHAN_MAX; i++) {
		_channel[i].avail = 0;
		_tchannel[i].avail = 0;
	}
}

int SoundGenPCJr::volumeCalc(SndGenChan *chan) {
	int8 attenuation, dissolveValue;

	const int8 *dissolveData;

	switch (_dissolveMethod) {
	case 2:
		dissolveData = dissolveDataV2;
		break;
	case 3:
	default:
		dissolveData = dissolveDataV3;
		break;
	}

	assert(chan);

	attenuation = chan->attenuation;
	if (attenuation != 0x0F) {	// != silence
		if (chan->dissolveCount != 0xFFFF) {
			dissolveValue = dissolveData[chan->dissolveCount];
			if (dissolveValue == -100) {	// if at end of list
				chan->dissolveCount = 0xFFFF;
				chan->attenuation = chan->attenuationCopy;
				attenuation = chan->attenuation;
			} else {
				chan->dissolveCount++;

				attenuation += dissolveValue;
				if (attenuation < 0)
					attenuation = 0;
				if (attenuation > 0x0F)
					attenuation = 0x0F;

				chan->attenuationCopy = attenuation;

				attenuation &= 0x0F;
				attenuation += _vm->getvar(vVolume);
				if (attenuation > 0x0F)
					attenuation = 0x0F;
			}
		}
		//if (computer_type == 2) && (attenuation < 8)
		if (attenuation < 8)
			attenuation += 2;
	}

	return attenuation;
}

// read the next channel data.. fill it in *tone
// if tone isn't touched.. it should be inited so it just plays silence
// return 0 if it's passing more data
// return -1 if it's passing nothing (end of data)
int SoundGenPCJr::getNextNote(int ch, Tone *tone) {
	SndGenChan *chan;
	const byte *data;

	assert(tone);
	assert(ch < CHAN_MAX);

	if (!_vm->getflag(fSoundOn))
		return -1;

	chan = &_channel[ch];
	if (!chan->avail)
		return -1;

	while ((chan->duration == 0) && (chan->duration != 0xFFFF)) {
		data = chan->data;

		// read the duration of the note
		chan->duration = READ_LE_UINT16(data);	// duration

		// if it's 0 then it's not going to be played
		// if it's 0xFFFF then the channel data has finished.
		if ((chan->duration != 0) && (chan->duration != 0xFFFF)) {
			// only tone channels dissolve
			if ((ch != 3) && (_dissolveMethod != 0))	// != noise??
				chan->dissolveCount = 0;

			// attenuation (volume)
			chan->attenuation = data[4] & 0xF;

			// frequency
			if (ch < (CHAN_MAX - 1)) {
				chan->freqCount = (uint16)data[2] & 0x3F;
				chan->freqCount <<= 4;
				chan->freqCount |= data[3] & 0x0F;

				chan->genType = kGenTone;
			} else {
				int noiseFreq;

				// check for white noise (1) or periodic (0)
				chan->genType = (data[3] & 0x04) ? kGenWhite : kGenPeriod;

				noiseFreq = data[3] & 0x03;

				switch (noiseFreq) {
				case 0:
					chan->freqCount = 32;
					break;
				case 1:
					chan->freqCount = 64;
					break;
				case 2:
					chan->freqCount = 128;
					break;
				case 3:
					chan->freqCount = _channel[2].freqCount * 2;
					break;
				}
			}
		}
		// data now points to the next data seg-a-ment
		chan->data += 5;
	}

	if (chan->duration != 0xFFFF) {
		tone->freqCount = chan->freqCount;
		tone->atten = volumeCalc(chan);	// calc volume, sent vol is different from saved vol
		tone->type = chan->genType;
		chan->duration--;
	} else {
		// kill channel
		chan->avail = 0;
		chan->attenuation = 0x0F;	// silent
		chan->attenuationCopy = 0x0F;	// dunno really

		return -1;
	}

	return 0;
}

// Formulas for noise generator
// bit0 = output

// noise feedback for white noise mode
#define FB_WNOISE 0x12000	// bit15.d(16bits) = bit0(out) ^ bit2
//#define FB_WNOISE 0x14000	// bit15.d(16bits) = bit0(out) ^ bit1
//#define FB_WNOISE 0x28000	// bit16.d(17bits) = bit0(out) ^ bit2 (same to AY-3-8910)
//#define FB_WNOISE 0x50000	// bit17.d(18bits) = bit0(out) ^ bit2

// noise feedback for periodic noise mode
// it is correct maybe (it was in the Megadrive sound manual)
//#define FB_PNOISE 0x10000	// 16bit rorate
#define FB_PNOISE 0x08000

// noise generator start preset (for periodic noise)
#define NG_PRESET 0x0f35

//#define WAVE_HEIGHT (0x7FFF)

// Volume table.
//
// 2dB = 20*log(a/b)
// 10^(2/20)*b = a;
// value = 0x7fff;
// value /= 1.258925411794;
const int16 volTable[16] = {
	32767, 26027, 20674, 16422, 13044, 10361, 8230, 6537, 5193, 4125, 3276, 2602, 2067, 1642, 1304, 0
};

#define FREQ_DIV 111844
#define MULT FREQ_DIV

// fill buff
int SoundGenPCJr::chanGen(int chan, int16 *stream, int len) {
	ToneChan *tpcm;
	Tone toneNew;
	int fillSize;
	int retVal;

	tpcm = &_tchannel[chan];

	retVal = -1;

	while (len > 0) {
		if (tpcm->noteCount <= 0) {
			// get new tone data
			toneNew.freqCount = 0;
			toneNew.atten = 0xF;
			toneNew.type = kGenTone;
			if ((tpcm->avail) && (getNextNote(chan, &toneNew) == 0)) {
				tpcm->atten = toneNew.atten;
				tpcm->freqCount = toneNew.freqCount;
				tpcm->genType = toneNew.type;

				// setup counters 'n stuff
				// SAMPLE_RATE samples per sec.. tone changes 60 times per sec
				tpcm->noteCount = SAMPLE_RATE / 60;
				retVal = 0;
			} else {
				// if it doesn't return an
				tpcm->genType = kGenSilence;
				tpcm->noteCount = len;
				tpcm->avail = 0;
			}
		}

		// write nothing
		if ((tpcm->freqCount == 0) || (tpcm->atten == 0xf)) {
			tpcm->genType = kGenSilence;
		}

		// find which is smaller.. the buffer or the
		fillSize = (tpcm->noteCount <= len) ? tpcm->noteCount : len;

		switch (tpcm->genType) {
			case kGenTone:
				fillSize = fillSquare(tpcm, stream, fillSize);
				break;
			case kGenPeriod:
			case kGenWhite:
				fillSize = fillNoise(tpcm, stream, fillSize);
				break;
			case kGenSilence:
			default:
				// fill with whitespace
				memset(stream, 0, fillSize * sizeof(int16));
				break;
		}

		tpcm->noteCount -= fillSize;
		stream += fillSize;
		len -= fillSize;
	}

	return retVal;
}

int SoundGenPCJr::fillSquare(ToneChan *t, int16 *buf, int len) {
	int count;

	if (t->genType != t->genTypePrev) {
		// make sure the freqCount is checked
		t->freqCountPrev = -1;
		t->sign = 1;
		t->genTypePrev = t->genType;
	}

	if (t->freqCount != t->freqCountPrev) {
		//t->scale = (int)( (double)t->samp->freq*t->freqCount/FREQ_DIV * MULT + 0.5);
		t->scale = (SAMPLE_RATE / 2) * t->freqCount;
		t->count = t->scale;
		t->freqCountPrev = t->freqCount;
	}

	count = len;

	while (count > 0) {
		*(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten];
		count--;

		// get next sample
		t->count -= MULT;
		while (t->count <= 0) {
			t->sign ^= 1;
			t->count += t->scale;
		}
	}

	return len;
}

int SoundGenPCJr::fillNoise(ToneChan *t, int16 *buf, int len) {
	int count;

	if (t->genType != t->genTypePrev) {
		// make sure the freqCount is checked
		t->freqCountPrev = -1;
		t->genTypePrev = t->genType;
	}

	if (t->freqCount != t->freqCountPrev) {
		//t->scale = (int)( (double)t->samp->freq*t->freqCount/FREQ_DIV * MULT + 0.5);
		t->scale = (SAMPLE_RATE / 2) * t->freqCount;
		t->count = t->scale;
		t->freqCountPrev = t->freqCount;

		t->feedback = (t->genType == kGenWhite) ? FB_WNOISE : FB_PNOISE;
		// reset noise shifter
		t->noiseState = NG_PRESET;
		t->sign = t->noiseState & 1;
	}

	count = len;

	while (count > 0) {
		*(buf++) = t->sign ? volTable[t->atten] : -volTable[t->atten];
		count--;

		// get next sample
		t->count -= MULT;
		while (t->count <= 0) {
			if (t->noiseState & 1)
				t->noiseState ^= t->feedback;

			t->noiseState >>= 1;
			t->sign = t->noiseState & 1;
			t->count += t->scale;
		}
	}

	return len;
}

int SoundGenPCJr::readBuffer(int16 *stream, const int len) {
	int streamCount;
	int16 *sPtr, *cPtr;

	if (_chanAllocated < len) {
		free(_chanData);
		_chanData = (int16 *)malloc(len << 1);
		_chanAllocated = len;
	}
	memset(stream, 0, len << 1);

	assert(stream);

	bool finished = true;

	for (int i = 0; i < CHAN_MAX; i++) {
		// get channel data(chan.userdata)
		if (chanGen(i, _chanData, len) == 0) {
			// divide by number of channels then add to stream
			streamCount = len;
			sPtr = stream;
			cPtr = _chanData;

			while (streamCount--)
				*(sPtr++) += *(cPtr++) / CHAN_MAX;

			finished = false;
		}
	}

	if (finished)
		_vm->_sound->soundIsFinished();

	return len;
}

} // End of namespace Agi
