// license:BSD-3-Clause
// copyright-holders:Antoine Mine
/**********************************************************************

  Copyright (C) Antoine Mine' 2006

  Philips / Signetics MEA 8000 emulation.

  The MEA 8000 is a speech synthesis chip.
  The French company TMPI (Techni-musique & parole informatique) provided
  speech extensions for several 8-bit computers (Thomson, Amstrad, Oric).
  It was quite popular in France because of its ability to spell 'u'
  (unlike the more widespread SPO 296 chip).

  The synthesis is based on a 4-formant model.
  First, an initial sawtooth noise signal is generated.
  The signal passes through a cascade of 4 filters of increasing frequency.
  Each filter is a second order digital filter with a programmable
  frequency and bandwidth.
  All parameters, including filter parameters, are smoothly interpolated
  for the duration of a frame (8ms, 16ms, 32ms, or 64 ms).

  TODO:
  - REQ output pin
  - optimize mea8000_compute_sample
  - should we accept new frames in slow-stop mode ?

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

#include "emu.h"
#include "mea8000.h"

#include <math.h>

//#define VERBOSE 1
#include "logmacro.h"


/******************* utilitiy function and macros ********************/


ALLOW_SAVE_TYPE( mea8000_device::mea8000_state );


/* digital filters work at 8 kHz */
#define F0 (clock() / 480)

/* filtered output is supersampled x 8 */
#define SUPERSAMPLING 8

/* actual output pediod */
#define SAMPLING attotime::from_hz((SUPERSAMPLING*F0))



/************************* quantization tables ***********************/


/* table amplitude [-QUANT,QUANT] */
#define QUANT 512

/* frequency, in Hz */

static const int fm1_table[32] =
{
	150,  162,  174,  188,  202,  217,  233,  250,
	267,  286,  305,  325,  346,  368,  391,  415,
	440,  466,  494,  523,  554,  587,  622,  659,
	698,  740,  784,  830,  880,  932,  988, 1047
};

static const int fm2_table[32] =
{
	440,  466,  494,  523,  554,  587,  622,  659,
	698,  740,  784,  830,  880,  932,  988, 1047,
	1100, 1179, 1254, 1337, 1428, 1528, 1639, 1761,
	1897, 2047, 2214, 2400, 2609, 2842, 3105, 3400
};

static const int fm3_table[8] =
{
	1179, 1337, 1528, 1761, 2047, 2400, 2842, 3400
};

static const int fm4_table[1] = { 3500 };



/* bandwidth, in Hz */
static const int bw_table[4] = { 726, 309, 125, 50 };



/* amplitude * 1000 */
static const int ampl_table[16] =
{
	0,   8,  11,  16,  22,  31,  44,   62,
	88, 125, 177, 250, 354, 500, 707, 1000
};



/* pitch increment, in Hz / 8 ms */
static const int pi_table[32] =
{
	0, 1,  2,  3,  4,  5,  6,  7,
	8, 9, 10, 11, 12, 13, 14, 15,
	0 /* noise */, -15, -14, -13, -12, -11, -10, -9,
	-8, -7, -6, -5, -4, -3, -2, -1
};



DEFINE_DEVICE_TYPE(MEA8000, mea8000_device, "mea8000", "Philips/Signetics MEA 8000 speech synthesizer")


mea8000_device::mea8000_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, MEA8000, tag, owner, clock),
	device_sound_interface(mconfig, *this),
	m_write_req(*this),
	m_stream(nullptr),
	m_output(0)
{
}


//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void mea8000_device::device_start()
{
	m_write_req.resolve_safe();

	init_tables();

	m_stream = stream_alloc(0, 1, clock() / 60);
	save_item(NAME(m_output));

	m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(mea8000_device::timer_expire),this));

	save_item(NAME(m_state));
	save_item(NAME(m_buf));
	save_item(NAME(m_bufpos));
	save_item(NAME(m_cont));
	save_item(NAME(m_roe));
	save_item(NAME(m_framelength));
	save_item(NAME(m_framepos));
	save_item(NAME(m_framelog));
	save_item(NAME(m_lastsample));
	save_item(NAME(m_sample));
	save_item(NAME(m_phi));
	for (int i = 0; i < 4; i++)
	{
		save_item(NAME(m_f[i].fm), i);
		save_item(NAME(m_f[i].last_fm), i);
		save_item(NAME(m_f[i].bw), i);
		save_item(NAME(m_f[i].last_bw), i);
		save_item(NAME(m_f[i].output), i);
		save_item(NAME(m_f[i].last_output), i);
	}
	save_item(NAME(m_last_ampl));
	save_item(NAME(m_ampl));
	save_item(NAME(m_last_pitch));
	save_item(NAME(m_pitch));
	save_item(NAME(m_noise));
}



/***************************** REQ **********************************/


int mea8000_device::accept_byte()
{
	return m_state == mea8000_state::STOPPED || m_state == mea8000_state::WAIT_FIRST || (m_state == mea8000_state::STARTED && m_bufpos < 4);
}

void mea8000_device::update_req()
{
	// actually, req pulses less than 3us for each new byte,
	// it goes back up if there space left in the buffer, or stays low if the
	// buffer contains a complete frame and the CPU nees to wait for the next
	// frame end to compose a new frame.
	m_write_req(accept_byte());
}



/*********************** sound generation ***************************/


/* precompute tables */
void mea8000_device::init_tables()
{
	for (int i = 0; i < TABLE_LEN; i++)
	{
		double f = (double)i / F0;
		m_cos_table[i]  = 2. * cos(2. * M_PI * f) * QUANT;
		m_exp_table[i]  = exp(-M_PI * f) * QUANT;
		m_exp2_table[i] = exp(-2 * M_PI * f) * QUANT;
	}
	for (auto & elem : m_noise_table)
		elem = (machine().rand() % (2 * QUANT)) - QUANT;
}


#ifndef MEA8000_FLOAT_MODE /* uint16_t version */



/* linear interpolation */
int mea8000_device::interp( uint16_t org, uint16_t dst )
{
	return org + (((dst - org) * m_framepos) >> m_framelog);
}


/* apply second order digital filter, sampling at F0 */
int mea8000_device::filter_step( int i, int input )
{
	/* frequency */
	int fm = interp(m_f[i].last_fm, m_f[i].fm);
	/* bandwidth */
	int bw = interp(m_f[i].last_bw, m_f[i].bw);
	/* filter coefficients */
	int b = (m_cos_table[fm] * m_exp_table[bw]) / QUANT;
	int c = m_exp2_table[bw];
	/* transfer function */
	int next_output = input + (b * m_f[i].output - c * m_f[i].last_output) / QUANT;
	m_f[i].last_output = m_f[i].output;
	m_f[i].output = next_output;
	return next_output;
}


/* random waveform, in [-QUANT,QUANT] */
int mea8000_device::noise_gen()
{
	m_phi = (m_phi + 1) % NOISE_LEN;
	return m_noise_table[m_phi];
}


/* sawtooth waveform at F0, in [-QUANT,QUANT] */
int mea8000_device::freq_gen()
{
	int pitch = interp(m_last_pitch, m_pitch);
	m_phi = (m_phi + pitch) % F0;
	return ((m_phi % F0) * QUANT * 2) / F0 - QUANT;
}


/* sample in [-32768,32767], at F0 */
int mea8000_device::compute_sample()
{
	int out;
	int ampl = interp(m_last_ampl, m_ampl);

	if (m_noise)
		out = noise_gen();
	else
		out = freq_gen();

	out *= ampl / 32;

	for (int i = 0; i < 4; i++)
		out = filter_step(i, out);

	if (out > 32767)
		out = 32767;
	if (out < -32767)
		out = -32767;
	return out;
}



#else /* float version */



/* linear interpolation */
double mea8000_device::interp(double org, double dst)
{
	return org + ((dst - org) * m_framepos) / m_framelength;
}


/* apply second order digital filter, sampling at F0 */
double mea8000_device::filter_step(int i, double input)
{
	double fm = interp(m_f[i].last_fm, m_f[i].fm);
	double bw = interp(m_f[i].last_bw, m_f[i].bw);
	double b = 2. * cos(2. * M_PI * fm / F0);
	double c = -exp(-M_PI * bw / F0);
	double next_output = input - c * (b * m_f[i].output + c * m_f[i].last_output);
	m_f[i].last_output = m_f[i].output;
	m_f[i].output = next_output;
	return next_output;
}


/* noise, in [-1,1] */
double mea8000_device::noise_gen()
{
	m_phi++;
	return (double) m_noise_table[m_phi % NOISE_LEN] / QUANT;
}



/* sawtooth waveform at F0, in [-1,1] */
double mea8000_device::freq_gen()
{
	int pitch = interp(m_last_pitch, m_pitch);
	m_phi += pitch;
	return (double) (m_phi % F0) / (F0 / 2.) - 1.;
}


/* sample in [-32767,32767], at F0 */
int mea8000_device::compute_sample()
{
	double out;
	double ampl = interp(8. * m_last_ampl, 8. * m_ampl);

	if (m_noise)
		out = noise_gen();
	else
		out = freq_gen();

	out *= ampl;

	for (int i = 0; i < 4; i++)
	{
		out = filter_step(i, out);
	}

	if (out > 32767)
		out = 32767;
	if (out < -32767)
		out = -32767;
	return out;
}


#endif


/*********************** frame management ***************************/



/* shift frame parameters from current to last */
void mea8000_device::shift_frame()
{
	m_last_pitch = m_pitch;
	for (auto & elem : m_f)
	{
		elem.last_bw = elem.bw;
		elem.last_fm = elem.fm;
	}
	m_last_ampl = m_ampl;
}



/* decode fields from buffer to current frame */
void mea8000_device::decode_frame()
{
	int fd = (m_buf[3] >> 5) & 3; /* 0=8ms, 1=16ms, 2=32ms, 3=64ms */
	int pi = pi_table[m_buf[3] & 0x1f] << fd;
	m_noise = (m_buf[3] & 0x1f) == 16;
	m_pitch = m_last_pitch + pi;
	m_f[0].bw = bw_table[m_buf[0] >> 6];
	m_f[1].bw = bw_table[(m_buf[0] >> 4) & 3];
	m_f[2].bw = bw_table[(m_buf[0] >> 2) & 3];
	m_f[3].bw = bw_table[m_buf[0] & 3];
	m_f[3].fm = fm4_table[0];
	m_f[2].fm = fm3_table[m_buf[1] >> 5];
	m_f[1].fm = fm2_table[m_buf[1] & 0x1f];
	m_f[0].fm = fm1_table[m_buf[2] >> 3];
	m_ampl = ampl_table[((m_buf[2] & 7) << 1) | (m_buf[3] >> 7)];
	m_framelog = fd + 6 /* 64 samples / ms */ + 3;
	m_framelength = 1 << m_framelog;
	m_bufpos = 0;
#ifdef MEA8000_FLOAT_MODE
	LOG("mea800_decode_frame: pitch=%i noise=%i  fm1=%gHz bw1=%gHz  fm2=%gHz bw2=%gHz  fm3=%gHz bw3=%gHz  fm4=%gHz bw4=%gHz  ampl=%g fd=%ims\n",
			m_pitch, m_noise,
			m_f[0].fm, m_f[0].bw, m_f[1].fm, m_f[1].bw,
			m_f[2].fm, m_f[2].bw, m_f[3].fm, m_f[3].bw,
			m_ampl/1000., 8 << fd);
#else
	LOG("mea800_decode_frame: pitch=%i noise=%i  fm1=%iHz bw1=%iHz  fm2=%iHz bw2=%iHz  fm3=%iHz bw3=%iHz  fm4=%iHz bw4=%iHz  ampl=%g fd=%ims\n",
			m_pitch, m_noise,
			m_f[0].fm, m_f[0].bw, m_f[1].fm, m_f[1].bw,
			m_f[2].fm, m_f[2].bw, m_f[3].fm, m_f[3].bw,
			m_ampl/1000., 8 << fd);
#endif
}



void mea8000_device::start_frame()
{
	/* enter or stay in active mode */
	m_timer->reset(SAMPLING);
	m_framepos = 0;
}



void mea8000_device::stop_frame()
{
	/* enter stop mode */
	m_timer->reset();
	m_state = mea8000_state::STOPPED;
	m_stream->update();
	m_output = 0;
}



void mea8000_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	for (int samp = 0; samp < samples; samp++)
	{
		outputs[0][samp] = m_output;
	}
}

/* next sample in frame, sampling at 64 kHz */
TIMER_CALLBACK_MEMBER( mea8000_device::timer_expire )
{
	int pos = m_framepos % SUPERSAMPLING;

	if (!pos)
	{
		/* sample is really computed only every 8-th time */
		m_lastsample = m_sample;
		m_sample = compute_sample();
		m_stream->update();
		m_output = m_lastsample;
	}
	else
	{
		/* other samples are simply interpolated */
		int sample = m_lastsample + ((pos * (m_sample-m_lastsample)) / SUPERSAMPLING);
		m_stream->update();
		m_output = sample;
	}

	m_framepos++;
	if (m_framepos >= m_framelength)
	{
		shift_frame();
		/* end of frame */
		if (m_bufpos == 4)
		{
			/* we have a successor */
			LOG("%f mea8000_timer_expire: new frame\n", machine().time().as_double());
			decode_frame();
			start_frame();
		}
		else if (m_cont)
		{
			/* repeat mode */
			LOG("%f mea8000_timer_expire: repeat frame\n", machine().time().as_double());
			start_frame();
		}
		/* slow stop */
		else if (m_state == mea8000_state::STARTED)
		{
			m_ampl = 0;
			LOG("%f mea8000_timer_expire: fade frame\n", machine().time().as_double());
			start_frame();
			m_state = mea8000_state::SLOWING;
		}
		else if (m_state == mea8000_state::SLOWING)
		{
			LOG("%f mea8000_timer_expire: stop frame\n", machine().time().as_double());
			stop_frame();
		}
		update_req();
	}
	else
	{
		/* continue frame */
		m_timer->reset(SAMPLING);
	}
}


/************************** CPU interface ****************************/


READ8_MEMBER( mea8000_device::read )
{
	switch (offset)
	{
	case 0: /* status register */
	case 1:
		/* ready to accept next frame */
#if 0
		LOG("%s %f: mea8000_r ready=%i\n", machine().describe_context(), machine().time().as_double(), accept_byte());
#endif
		return accept_byte() << 7;

	default:
		logerror("%s mea8000_r invalid read offset %i\n", machine().describe_context(), offset);
	}
	return 0;
}

WRITE8_MEMBER( mea8000_device::write )
{
	switch (offset)
	{
	case 0: /* data register */
		if (m_state == mea8000_state::STOPPED)
		{
			/* got pitch byte before first frame */
			m_pitch = 2 * data;
			LOG("%s %f: mea8000_w pitch %i\n", machine().describe_context(), machine().time().as_double(), m_pitch);
			m_state = mea8000_state::WAIT_FIRST;
			m_bufpos = 0;
		}
		else if (m_bufpos == 4)
		{
			/* overflow */
			LOG("%s %f: mea8000_w data overflow %02X\n", machine().describe_context(), machine().time().as_double(), data);
		}
		else
		{
			/* enqueue frame byte */
			LOG("%s %f: mea8000_w data %02X in frame pos %i\n", machine().describe_context(), machine().time().as_double(),
					data, m_bufpos);
			m_buf[m_bufpos] = data;
			m_bufpos++;
			if (m_bufpos == 4 && m_state == mea8000_state::WAIT_FIRST)
			{
				/* fade-in first frame */
				int old_pitch = m_pitch;
				m_last_pitch = old_pitch;
				decode_frame();
				shift_frame();
				m_last_pitch = old_pitch;
				m_ampl = 0;
				start_frame();
				m_state = mea8000_state::STARTED;
			}
		}
		update_req();
		break;

	case 1: /* command register */
	{
		int stop = BIT(data, 4);

		if (data & 8)
			m_cont = BIT(data, 2);

		if (data & 2)
			m_roe = BIT(data, 0);

		if (stop)
			stop_frame();

		LOG( "%s %f: mea8000_w command %02X stop=%i cont=%i roe=%i\n",
				machine().describe_context(), machine().time().as_double(), data,
				stop, m_cont, m_roe);

		update_req();
		break;
	}

	default:
		logerror( "%s mea8000_w invalid write offset %i\n", machine().describe_context(), offset);
	}
}
