// license:BSD-3-Clause
// copyright-holders:Philip Bennett
/***************************************************************************
Microprose sound hardware
Bit of a rush job - needs to be implemented properly eventually
***************************************************************************/
#include "emu.h"
#include "audio/micro3d.h"
#define MM5837_CLOCK 100000
/*************************************
*
* Pink noise filtering
*
*************************************/
/* Borrowed from segasnd.c */
inline void micro3d_sound_device::m3d_filter_state::configure(double r, double c)
{
capval = 0;
exponent = 1.0 - exp(-1.0 / (r * c * 2000000/8));
}
#if 0
inline double micro3d_sound_device::m3d_filter_state::step_rc_filter(double input)
{
capval += (input - capval) * exponent;
return capval;
}
inline double micro3d_sound_device::m3d_filter_state::step_cr_filter(double input)
{
double const result = input - capval;
capval += result * exponent;
return result;
}
#endif
/*************************************
*
* SSM2047 simulation
*
*************************************/
void micro3d_sound_device::lp_filter::init(double fsval)
{
/* Section 1 */
proto_coef[0].a0 = 1.0;
proto_coef[0].a1 = 0;
proto_coef[0].a2 = 0;
proto_coef[0].b0 = 1.0;
proto_coef[0].b1 = 0.765367;
proto_coef[0].b2 = 1.0;
/* Section 2 */
proto_coef[1].a0 = 1.0;
proto_coef[1].a1 = 0;
proto_coef[1].a2 = 0;
proto_coef[1].b0 = 1.0;
proto_coef[1].b1 = 1.847759;
proto_coef[1].b2 = 1.0;
coef = make_unique_clear<float[]>(4 * 2 + 1);
fs = fsval;
history = make_unique_clear<float[]>(2 * 2);
}
static void prewarp(double *a0, double *a1, double *a2,double fc, double fs)
{
double wp, pi;
pi = 4.0 * atan(1.0);
wp = 2.0 * fs * tan(pi * fc / fs);
*a2 = *a2 / (wp * wp);
*a1 = *a1 / wp;
}
static void bilinear(double a0, double a1, double a2,
double b0, double b1, double b2,
double *k, double fs, float *coef)
{
double ad, bd;
ad = 4. * a2 * fs * fs + 2. * a1 * fs + a0;
bd = 4. * b2 * fs * fs + 2. * b1* fs + b0;
*k *= ad/bd;
*coef++ = (2. * b0 - 8. * b2 * fs * fs) / bd;
*coef++ = (4. * b2 * fs * fs - 2. * b1 * fs + b0) / bd;
*coef++ = (2. * a0 - 8. * a2 * fs * fs) / ad;
*coef = (4. * a2 * fs * fs - 2. * a1 * fs + a0) / ad;
}
void micro3d_sound_device::lp_filter::recompute(double k, double q, double fc)
{
float *c = coef.get() + 1;
for (int nInd = 0; nInd < 2; nInd++)
{
double a0 = proto_coef[nInd].a0;
double a1 = proto_coef[nInd].a1;
double a2 = proto_coef[nInd].a2;
double b0 = proto_coef[nInd].b0;
double b1 = proto_coef[nInd].b1 / q;
double b2 = proto_coef[nInd].b2;
prewarp(&a0, &a1, &a2, fc, fs);
prewarp(&b0, &b1, &b2, fc, fs);
bilinear(a0, a1, a2, b0, b1, b2, &k, fs, c);
c += 4;
}
coef[0] = k;
}
void micro3d_sound_device::noise_sh_w(u8 data)
{
if (~data & 8)
{
if (m_dac_data != m_dac[data & 3])
{
double q;
double fc;
m_stream->update();
m_dac[data & 3] = m_dac_data;
if (m_dac[VCA] == 255)
m_gain = 0;
else
m_gain = expf(-(float)(m_dac[VCA]) / 25.0f) * 10.0f;
q = 0.75/255 * (255 - m_dac[VCQ]) + 0.1;
fc = 4500.0/255 * (255 - m_dac[VCF]) + 100;
m_filter.recompute(m_gain, q, fc);
}
}
}
/*************************************
*
* Initialisation
*
*************************************/
DEFINE_DEVICE_TYPE(MICRO3D_SOUND, micro3d_sound_device, "micro3d_sound", "Microprose Custom Sound")
micro3d_sound_device::micro3d_sound_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
: device_t(mconfig, MICRO3D_SOUND, tag, owner, clock),
device_sound_interface(mconfig, *this),
m_gain(0),
m_noise_shift(0),
m_noise_value(0),
m_noise_subcount(0),
m_stream(nullptr)
{
memset(m_dac, 0, sizeof(u8)*4);
}
//-------------------------------------------------
// device_start - device-specific startup
//-------------------------------------------------
void micro3d_sound_device::device_start()
{
/* Allocate the stream */
m_stream = machine().sound().stream_alloc(*this, 0, 2, machine().sample_rate());
m_filter.init(machine().sample_rate());
m_noise_filters[0].configure(2.7e3 + 2.7e3, 1.0e-6);
m_noise_filters[1].configure(2.7e3 + 1e3, 0.30e-6);
m_noise_filters[2].configure(2.7e3 + 270, 0.15e-6);
m_noise_filters[3].configure(2.7e3 + 0, 0.082e-6);
// m_noise_filters[4].configure(33e3, 0.1e-6);
}
//-------------------------------------------------
// device_reset - device-specific reset
//-------------------------------------------------
void micro3d_sound_device::device_reset()
{
m_noise_shift = 0x15555;
m_dac[0] = 255;
m_dac[1] = 255;
m_dac[2] = 255;
m_dac[3] = 255;
}
//-------------------------------------------------
// sound_stream_update - handle a stream update
//-------------------------------------------------
void micro3d_sound_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
lp_filter *iir = &m_filter;
float pan_l, pan_r;
stream_sample_t *fl = &outputs[0][0];
stream_sample_t *fr = &outputs[1][0];
/* Clear the buffers */
memset(outputs[0], 0, samples * sizeof(*outputs[0]));
memset(outputs[1], 0, samples * sizeof(*outputs[1]));
if (m_gain == 0)
return;
pan_l = (float)(255 - m_dac[PAN]) / 255.0f;
pan_r = (float)(m_dac[PAN]) / 255.0f;
while (samples--)
{
unsigned int i;
float *hist1_ptr,*hist2_ptr,*coef_ptr;
float output,new_hist,history1,history2;
float input, white;
int step;
/* Update the noise source */
for (step = 2000000 / (2000000/8); step >= m_noise_subcount; step -= m_noise_subcount)
{
m_noise_shift = (m_noise_shift << 1) | (((m_noise_shift >> 13) ^ (m_noise_shift >> 16)) & 1);
m_noise_value = (m_noise_shift >> 16) & 1;
m_noise_subcount = 2000000 / MM5837_CLOCK;
}
m_noise_subcount -= step;
input = (float)m_noise_value - 0.5f;
white = input;
/* Pink noise filtering */
m_noise_filters[0].capval = 0.99765 * m_noise_filters[0].capval + input * 0.0990460;
m_noise_filters[1].capval = 0.96300 * m_noise_filters[1].capval + input * 0.2965164;
m_noise_filters[2].capval = 0.57000 * m_noise_filters[2].capval + input * 1.0526913;
input = m_noise_filters[0].capval + m_noise_filters[1].capval + m_noise_filters[2].capval + input * 0.1848;
input += white;
input *= 200.0f;
coef_ptr = iir->coef.get();
hist1_ptr = iir->history.get();
hist2_ptr = hist1_ptr + 1;
/* 1st number of coefficients array is overall input scale factor, * or filter gain */
output = input * (*coef_ptr++);
for (i = 0 ; i < 2; i++)
{
history1 = *hist1_ptr;
history2 = *hist2_ptr;
output = output - history1 * (*coef_ptr++);
new_hist = output - history2 * (*coef_ptr++);
output = new_hist + history1 * (*coef_ptr++);
output = output + history2 * (*coef_ptr++);
*hist2_ptr++ = *hist1_ptr;
*hist1_ptr++ = new_hist;
hist1_ptr++;
hist2_ptr++;
}
output *= 3.5f;
/* Clip */
if (output > 32767)
output = 32767;
else if (output < -32768)
output = -32768;
*fl++ = output * pan_l;
*fr++ = output * pan_r;
}
}