// license:BSD-3-Clause
// copyright-holders:R. Belmont
/*
ES5503 - Ensoniq ES5503 "DOC" emulator v2.1.1
By R. Belmont.
Copyright R. Belmont.
History: the ES5503 was the next design after the famous C64 "SID" by Bob Yannes.
It powered the legendary Mirage sampler (the first affordable pro sampler) as well
as the ESQ-1 synth/sequencer. The ES5505 (used in Taito's F3 System) and 5506
(used in the "Soundscape" series of ISA PC sound cards) followed on a fundamentally
similar architecture.
Bugs: On the real silicon, oscillators 30 and 31 have random volume fluctuations and are
unusable for playback. We don't attempt to emulate that. :-)
Additionally, in "swap" mode, there's one cycle when the switch takes place where the
oscillator's output is 0x80 (centerline) regardless of the sample data. This can
cause audible clicks and a general degradation of audio quality if the correct sample
data at that point isn't 0x80 or very near it.
Changes:
0.2 (RB) - improved behavior for volumes > 127, fixes missing notes in Nucleus & missing voices in Thexder
0.3 (RB) - fixed extraneous clicking, improved timing behavior for e.g. Music Construction Set & Music Studio
0.4 (RB) - major fixes to IRQ semantics and end-of-sample handling.
0.5 (RB) - more flexible wave memory hookup (incl. banking) and save state support.
1.0 (RB) - properly respects the input clock
2.0 (RB) - C++ conversion, more accurate oscillator IRQ timing
2.1 (RB) - Corrected phase when looping; synthLAB, Arkanoid, and Arkanoid II no longer go out of tune
2.1.1 (RB) - Fixed issue introduced in 2.0 where IRQs were delayed
*/
#include "emu.h"
#include "es5503.h"
// device type definition
DEFINE_DEVICE_TYPE(ES5503, es5503_device, "es5503", "Ensoniq ES5503")
// useful constants
static constexpr uint16_t wavesizes[8] = { 256, 512, 1024, 2048, 4096, 8192, 16384, 32768 };
static constexpr uint32_t wavemasks[8] = { 0x1ff00, 0x1fe00, 0x1fc00, 0x1f800, 0x1f000, 0x1e000, 0x1c000, 0x18000 };
static constexpr uint32_t accmasks[8] = { 0xff, 0x1ff, 0x3ff, 0x7ff, 0xfff, 0x1fff, 0x3fff, 0x7fff };
static constexpr int resshifts[8] = { 9, 10, 11, 12, 13, 14, 15, 16 };
//**************************************************************************
// LIVE DEVICE
//**************************************************************************
//-------------------------------------------------
// es5503_device - constructor
//-------------------------------------------------
es5503_device::es5503_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
: device_t(mconfig, ES5503, tag, owner, clock),
device_sound_interface(mconfig, *this),
device_rom_interface(mconfig, *this, 17),
m_irq_func(*this),
m_adc_func(*this)
{
}
//-------------------------------------------------
// device_timer - called when our device timer expires
//-------------------------------------------------
void es5503_device::device_timer(emu_timer &timer, device_timer_id tid, int param, void *ptr)
{
m_stream->update();
}
//-------------------------------------------------
// rom_bank_updated - the rom bank has changed
//-------------------------------------------------
void es5503_device::rom_bank_updated()
{
m_stream->update();
}
// halt_osc: handle halting an oscillator
// chip = chip ptr
// onum = oscillator #
// type = 1 for 0 found in sample data, 0 for hit end of table size
void es5503_device::halt_osc(int onum, int type, uint32_t *accumulator, int resshift)
{
ES5503Osc *pOsc = &oscillators[onum];
ES5503Osc *pPartner = &oscillators[onum^1];
int mode = (pOsc->control>>1) & 3;
// if 0 found in sample data or mode is not free-run, halt this oscillator
if ((mode != MODE_FREE) || (type != 0))
{
pOsc->control |= 1;
}
else // preserve the relative phase of the oscillator when looping
{
uint16_t wtsize = pOsc->wtsize - 1;
uint32_t altram = (*accumulator) >> resshift;
if (altram > wtsize)
{
altram -= wtsize;
}
else
{
altram = 0;
}
*accumulator = altram << resshift;
}
// if swap mode, start the partner
if (mode == MODE_SWAP)
{
pPartner->control &= ~1; // clear the halt bit
pPartner->accumulator = 0; // and make sure it starts from the top (does this also need phase preservation?)
}
// IRQ enabled for this voice?
if (pOsc->control & 0x08)
{
pOsc->irqpend = 1;
m_irq_func(1);
}
}
void es5503_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
static int32_t mix[(44100/60)*2*8];
int32_t *mixp;
int osc, snum, i;
uint32_t ramptr;
assert(samples < (44100/60)*2);
memset(mix, 0, sizeof(mix));
for (int chan = 0; chan < output_channels; chan++)
{
for (osc = 0; osc < (oscsenabled+1); osc++)
{
ES5503Osc *pOsc = &oscillators[osc];
if (!(pOsc->control & 1) && ((pOsc->control >> 4) & (output_channels - 1)) == chan)
{
uint32_t wtptr = pOsc->wavetblpointer & wavemasks[pOsc->wavetblsize], altram;
uint32_t acc = pOsc->accumulator;
uint16_t wtsize = pOsc->wtsize - 1;
uint8_t ctrl = pOsc->control;
uint16_t freq = pOsc->freq;
int16_t vol = pOsc->vol;
int8_t data = -128;
int resshift = resshifts[pOsc->resolution] - pOsc->wavetblsize;
uint32_t sizemask = accmasks[pOsc->wavetblsize];
mixp = &mix[0] + chan;
for (snum = 0; snum < samples; snum++)
{
altram = acc >> resshift;
ramptr = altram & sizemask;
acc += freq;
// channel strobe is always valid when reading; this allows potentially banking per voice
m_channel_strobe = (ctrl>>4) & 0xf;
data = (int32_t)read_byte(ramptr + wtptr) ^ 0x80;
if (read_byte(ramptr + wtptr) == 0x00)
{
halt_osc(osc, 1, &acc, resshift);
}
else
{
*mixp += data * vol;
mixp += output_channels;
if (altram >= wtsize)
{
halt_osc(osc, 0, &acc, resshift);
}
}
// if oscillator halted, we've got no more samples to generate
if (pOsc->control & 1)
{
ctrl |= 1;
break;
}
}
pOsc->control = ctrl;
pOsc->accumulator = acc;
pOsc->data = data ^ 0x80;
}
}
}
mixp = &mix[0];
for (i = 0; i < samples; i++)
for (int chan = 0; chan < output_channels; chan++)
outputs[chan][i] = (*mixp++)>>1;
}
void es5503_device::device_start()
{
int osc;
m_irq_func.resolve_safe();
m_adc_func.resolve_safe(0);
rege0 = 0xff;
for (osc = 0; osc < 32; osc++)
{
save_item(NAME(oscillators[osc].freq), osc);
save_item(NAME(oscillators[osc].wtsize), osc);
save_item(NAME(oscillators[osc].control), osc);
save_item(NAME(oscillators[osc].vol), osc);
save_item(NAME(oscillators[osc].data), osc);
save_item(NAME(oscillators[osc].wavetblpointer), osc);
save_item(NAME(oscillators[osc].wavetblsize), osc);
save_item(NAME(oscillators[osc].resolution), osc);
save_item(NAME(oscillators[osc].accumulator), osc);
save_item(NAME(oscillators[osc].irqpend), osc);
}
output_rate = (clock()/8)/34; // (input clock / 8) / # of oscs. enabled + 2
m_stream = machine().sound().stream_alloc(*this, 0, output_channels, output_rate);
m_timer = timer_alloc(0, nullptr);
m_timer->adjust(attotime::from_hz(output_rate), 0, attotime::from_hz(output_rate));
}
void es5503_device::device_reset()
{
rege0 = 0xff;
for (auto & elem : oscillators)
{
elem.freq = 0;
elem.wtsize = 0;
elem.control = 0;
elem.vol = 0;
elem.data = 0x80;
elem.wavetblpointer = 0;
elem.wavetblsize = 0;
elem.resolution = 0;
elem.accumulator = 0;
elem.irqpend = 0;
}
oscsenabled = 1;
m_channel_strobe = 0;
output_rate = (clock()/8)/34; // (input clock / 8) / # of oscs. enabled + 2
}
READ8_MEMBER( es5503_device::read )
{
uint8_t retval;
int i;
m_stream->update();
if (offset < 0xe0)
{
int osc = offset & 0x1f;
switch(offset & 0xe0)
{
case 0: // freq lo
return (oscillators[osc].freq & 0xff);
case 0x20: // freq hi
return (oscillators[osc].freq >> 8);
case 0x40: // volume
return oscillators[osc].vol;
case 0x60: // data
return oscillators[osc].data;
case 0x80: // wavetable pointer
return (oscillators[osc].wavetblpointer>>8) & 0xff;
case 0xa0: // oscillator control
return oscillators[osc].control;
case 0xc0: // bank select / wavetable size / resolution
retval = 0;
if (oscillators[osc].wavetblpointer & 0x10000)
{
retval |= 0x40;
}
retval |= (oscillators[osc].wavetblsize<<3);
retval |= oscillators[osc].resolution;
return retval;
}
}
else // global registers
{
switch (offset)
{
case 0xe0: // interrupt status
retval = rege0;
m_irq_func(0);
// scan all oscillators
for (i = 0; i < oscsenabled+1; i++)
{
if (oscillators[i].irqpend)
{
// signal this oscillator has an interrupt
retval = i<<1;
rege0 = retval | 0x80;
// and clear its flag
oscillators[i].irqpend = 0;
break;
}
}
// if any oscillators still need to be serviced, assert IRQ again immediately
for (i = 0; i < oscsenabled+1; i++)
{
if (oscillators[i].irqpend)
{
m_irq_func(1);
break;
}
}
return retval;
case 0xe1: // oscillator enable
return oscsenabled<<1;
case 0xe2: // A/D converter
return m_adc_func();
}
}
return 0;
}
WRITE8_MEMBER( es5503_device::write )
{
m_stream->update();
if (offset < 0xe0)
{
int osc = offset & 0x1f;
switch(offset & 0xe0)
{
case 0: // freq lo
oscillators[osc].freq &= 0xff00;
oscillators[osc].freq |= data;
break;
case 0x20: // freq hi
oscillators[osc].freq &= 0x00ff;
oscillators[osc].freq |= (data<<8);
break;
case 0x40: // volume
oscillators[osc].vol = data;
break;
case 0x60: // data - ignore writes
break;
case 0x80: // wavetable pointer
oscillators[osc].wavetblpointer = (data<<8);
break;
case 0xa0: // oscillator control
// if a fresh key-on, reset the ccumulator
if ((oscillators[osc].control & 1) && (!(data&1)))
{
oscillators[osc].accumulator = 0;
}
oscillators[osc].control = data;
break;
case 0xc0: // bank select / wavetable size / resolution
if (data & 0x40) // bank select - not used on the Apple IIgs
{
oscillators[osc].wavetblpointer |= 0x10000;
}
else
{
oscillators[osc].wavetblpointer &= 0xffff;
}
oscillators[osc].wavetblsize = ((data>>3) & 7);
oscillators[osc].wtsize = wavesizes[oscillators[osc].wavetblsize];
oscillators[osc].resolution = (data & 7);
break;
}
}
else // global registers
{
switch (offset)
{
case 0xe0: // interrupt status
break;
case 0xe1: // oscillator enable
oscsenabled = (data>>1) & 0x1f;
output_rate = (clock()/8)/(2+oscsenabled);
m_stream->set_sample_rate(output_rate);
m_timer->adjust(attotime::from_hz(output_rate), 0, attotime::from_hz(output_rate));
break;
case 0xe2: // A/D converter
break;
}
}
}