mirror of
https://github.com/holub/mame
synced 2025-10-04 08:28:39 +03:00
2266 lines
65 KiB
C++
2266 lines
65 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Aaron Giles
|
|
/**********************************************************************************************
|
|
|
|
Ensoniq ES5505/6 driver
|
|
by Aaron Giles
|
|
|
|
Ensoniq OTIS - ES5505 Ensoniq OTTO - ES5506
|
|
|
|
OTIS is a VLSI device designed in a 2 micron double metal OTTO is a VLSI device designed in a 1.5 micron double metal
|
|
CMOS process. The device is the next generation of audio CMOS process. The device is the next generation of audio
|
|
technology from ENSONIQ. This new chip achieves a new technology from ENSONIQ. All calculations in the device are
|
|
level of audio fidelity performance. These improvements made with at least 18-bit accuracy.
|
|
are achieved through the use of frequency interpolation
|
|
and on board real time digital filters. All calculations The major features of OTTO are:
|
|
in the device are made with at least 16 bit accuracy. - 68 pin PLCC package
|
|
- On chip real time digital filters
|
|
The major features of OTIS are: - Frequency interpolation
|
|
- 48 Pin dual in line package - 32 independent voices
|
|
- On chip real time digital filters - Loop start and stop posistions for each voice
|
|
- Frequency interpolation - Bidirectional and reverse looping
|
|
- 32 independent voices (up from 25 in DOCII) - 68000 compatibility for asynchronous bus communication
|
|
- Loop start and stop positions for each voice - separate host and sound memory interface
|
|
- Bidirectional and reverse looping - 6 channel stereo serial communication port
|
|
- 68000 compatibility for asynchronous bus communication - Programmable clocks for defining serial protocol
|
|
- On board pulse width modulation D to A - Internal volume multiplication and stereo panning
|
|
- 4 channel stereo serial communication port - A to D input for pots and wheels
|
|
- Internal volume multiplication and stereo panning - Hardware support for envelopes
|
|
- A to D input for pots and wheels - Support for dual OTTO systems
|
|
- Up to 10MHz operation - Optional compressed data format for sample data
|
|
- Up to 16MHz operation
|
|
______ ______
|
|
_|o \__/ |_
|
|
A17/D13 - |_|1 48|_| - VSS A A A A A A
|
|
_| |_ 2 1 1 1 1 1 A
|
|
A18/D14 - |_|2 47|_| - A16/D12 0 9 8 7 6 5 1
|
|
_| |_ / / / / / / 4
|
|
A19/D15 - |_|3 46|_| - A15/D11 H H H H H H H V V H D D D D D D /
|
|
_| |_ D D D D D D D S D D 1 1 1 1 1 1 D
|
|
BS - |_|4 45|_| - A14/D10 0 1 2 3 4 5 6 S D 7 5 4 3 2 1 0 9
|
|
_| |_ ------------------------------------+
|
|
PWZERO - |_|5 44|_| - A13/D9 / 9 8 7 6 5 4 3 2 1 6 6 6 6 6 6 6 6 |
|
|
_| |_ / 8 7 6 5 4 3 2 1 |
|
|
SER0 - |_|6 43|_| - A12/D8 | |
|
|
_| E |_ SER0|10 60|A13/D8
|
|
SER1 - |_|7 N 42|_| - A11/D7 SER1|11 59|A12/D7
|
|
_| S |_ SER2|12 58|A11/D6
|
|
SER2 - |_|8 O 41|_| - A10/D6 SER3|13 ENSONIQ 57|A10/D5
|
|
_| N |_ SER4|14 56|A9/D4
|
|
SER3 - |_|9 I 40|_| - A9/D5 SER5|15 55|A8/D3
|
|
_| Q |_ WCLK|16 54|A7/D2
|
|
SERWCLK - |_|10 39|_| - A8/D4 LRCLK|17 ES5506 53|A6/D1
|
|
_| |_ BCLK|18 52|A5/D0
|
|
SERLR - |_|11 38|_| - A7/D3 RESB|19 51|A4
|
|
_| |_ HA5|20 50|A3
|
|
SERBCLK - |_|12 E 37|_| - A6/D2 HA4|21 OTTO 49|A2
|
|
_| S |_ HA3|22 48|A1
|
|
RLO - |_|13 5 36|_| - A5/D1 HA2|23 47|A0
|
|
_| 5 |_ HA1|24 46|BS1
|
|
RHI - |_|14 0 35|_| - A4/D0 HA0|25 45|BS0
|
|
_| 5 |_ POT_IN|26 44|DTACKB
|
|
LLO - |_|15 34|_| - CLKIN | 2 2 2 3 3 3 3 3 3 3 3 3 3 4 4 4 4 |
|
|
_| |_ | 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 |
|
|
LHI - |_|16 33|_| - CAS +--------------------------------------+
|
|
_| |_ B E E B E B B D S B B B E K B W W
|
|
POT - |_|17 O 32|_| - AMUX S B L N L S S D S S X S L Q / /
|
|
_| T |_ E E R E H M C V V A U A C R R R
|
|
DTACK - |_|18 I 31|_| - RAS R R D H R M C I M
|
|
_| S |_ _ D A
|
|
R/W - |_|19 30|_| - E T
|
|
_| |_ O
|
|
MS - |_|20 29|_| - IRQ P
|
|
_| |_
|
|
CS - |_|21 28|_| - A3
|
|
_| |_
|
|
RES - |_|22 27|_| - A2
|
|
_| |_
|
|
VSS - |_|23 26|_| - A1
|
|
_| |_
|
|
VDD - |_|24 25|_| - A0
|
|
|________________|
|
|
|
|
***********************************************************************************************/
|
|
|
|
#include "emu.h"
|
|
#include "es5506.h"
|
|
|
|
#if ES5506_MAKE_WAVS
|
|
#include "sound/wavwrite.h"
|
|
#endif
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
CONSTANTS
|
|
|
|
***********************************************************************************************/
|
|
|
|
#define VERBOSE 0
|
|
#include "logmacro.h"
|
|
|
|
#define RAINE_CHECK 0
|
|
|
|
static constexpr unsigned FINE_FILTER_BIT = 16;
|
|
static constexpr unsigned FILTER_BIT = 12;
|
|
static constexpr unsigned FILTER_SHIFT = FINE_FILTER_BIT - FILTER_BIT;
|
|
|
|
static constexpr unsigned MAX_SAMPLE_CHUNK = 10000;
|
|
static constexpr unsigned ULAW_MAXBITS = 8;
|
|
|
|
namespace {
|
|
|
|
enum : u16 {
|
|
CONTROL_BS1 = 0x8000,
|
|
CONTROL_BS0 = 0x4000,
|
|
CONTROL_CMPD = 0x2000,
|
|
CONTROL_CA2 = 0x1000,
|
|
CONTROL_CA1 = 0x0800,
|
|
CONTROL_CA0 = 0x0400,
|
|
CONTROL_LP4 = 0x0200,
|
|
CONTROL_LP3 = 0x0100,
|
|
CONTROL_IRQ = 0x0080,
|
|
CONTROL_DIR = 0x0040,
|
|
CONTROL_IRQE = 0x0020,
|
|
CONTROL_BLE = 0x0010,
|
|
CONTROL_LPE = 0x0008,
|
|
CONTROL_LEI = 0x0004,
|
|
CONTROL_STOP1 = 0x0002,
|
|
CONTROL_STOP0 = 0x0001,
|
|
|
|
CONTROL_BSMASK = (CONTROL_BS1 | CONTROL_BS0),
|
|
CONTROL_CAMASK = (CONTROL_CA2 | CONTROL_CA1 | CONTROL_CA0),
|
|
CONTROL_LPMASK = (CONTROL_LP4 | CONTROL_LP3),
|
|
CONTROL_LOOPMASK = (CONTROL_BLE | CONTROL_LPE),
|
|
CONTROL_STOPMASK = (CONTROL_STOP1 | CONTROL_STOP0),
|
|
|
|
// ES5505 has sightly different control bit
|
|
CONTROL_5505_LP4 = 0x0800,
|
|
CONTROL_5505_LP3 = 0x0400,
|
|
CONTROL_5505_CA1 = 0x0200,
|
|
CONTROL_5505_CA0 = 0x0100,
|
|
|
|
CONTROL_5505_LPMASK = (CONTROL_5505_LP4 | CONTROL_5505_LP3),
|
|
CONTROL_5505_CAMASK = (CONTROL_5505_CA1 | CONTROL_5505_CA0)
|
|
};
|
|
|
|
}
|
|
|
|
es550x_device::es550x_device(const machine_config &mconfig, device_type type, const char *tag, device_t *owner, u32 clock)
|
|
: device_t(mconfig, type, tag, owner, clock)
|
|
, device_sound_interface(mconfig, *this)
|
|
, m_stream(nullptr)
|
|
, m_sample_rate(0)
|
|
, m_master_clock(0)
|
|
, m_current_page(0)
|
|
, m_active_voices(0)
|
|
, m_mode(0)
|
|
, m_irqv(0)
|
|
, m_scratch(nullptr)
|
|
, m_ulaw_lookup(nullptr)
|
|
, m_volume_lookup(nullptr)
|
|
#if ES5506_MAKE_WAVS
|
|
, m_wavraw(nullptr)
|
|
#endif
|
|
, m_region0(nullptr)
|
|
, m_region1(nullptr)
|
|
, m_region2(nullptr)
|
|
, m_region3(nullptr)
|
|
, m_channels(0)
|
|
, m_irq_cb(*this)
|
|
, m_read_port_cb(*this)
|
|
, m_sample_rate_changed_cb(*this)
|
|
{
|
|
for (auto & elem : m_region_base)
|
|
{
|
|
elem = nullptr;
|
|
}
|
|
}
|
|
|
|
DEFINE_DEVICE_TYPE(ES5506, es5506_device, "es5506", "Ensoniq ES5506")
|
|
|
|
es5506_device::es5506_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: es550x_device(mconfig, ES5506, tag, owner, clock)
|
|
, m_write_latch(0)
|
|
, m_read_latch(0)
|
|
, m_wst(0)
|
|
, m_wend(0)
|
|
, m_lrend(0)
|
|
{
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_start - device-specific startup
|
|
//-------------------------------------------------
|
|
void es550x_device::device_start()
|
|
{
|
|
/* initialize the rest of the structure */
|
|
m_master_clock = clock();
|
|
m_irq_cb.resolve();
|
|
m_read_port_cb.resolve();
|
|
m_sample_rate_changed_cb.resolve();
|
|
m_irqv = 0x80;
|
|
|
|
/* compute the tables */
|
|
compute_tables();
|
|
|
|
/* allocate memory */
|
|
m_scratch = make_unique_clear<s32[]>(2 * MAX_SAMPLE_CHUNK);
|
|
|
|
/* register save */
|
|
save_item(NAME(m_sample_rate));
|
|
|
|
save_item(NAME(m_current_page));
|
|
save_item(NAME(m_active_voices));
|
|
save_item(NAME(m_mode));
|
|
save_item(NAME(m_irqv));
|
|
|
|
save_pointer(NAME(m_scratch), 2 * MAX_SAMPLE_CHUNK);
|
|
|
|
for (int j = 0; j < 32; j++)
|
|
{
|
|
save_item(NAME(m_voice[j].control), j);
|
|
save_item(NAME(m_voice[j].freqcount), j);
|
|
save_item(NAME(m_voice[j].start), j);
|
|
save_item(NAME(m_voice[j].end), j);
|
|
save_item(NAME(m_voice[j].accum), j);
|
|
save_item(NAME(m_voice[j].lvol), j);
|
|
save_item(NAME(m_voice[j].rvol), j);
|
|
save_item(NAME(m_voice[j].k2), j);
|
|
save_item(NAME(m_voice[j].k1), j);
|
|
save_item(NAME(m_voice[j].o4n1), j);
|
|
save_item(NAME(m_voice[j].o3n1), j);
|
|
save_item(NAME(m_voice[j].o3n2), j);
|
|
save_item(NAME(m_voice[j].o2n1), j);
|
|
save_item(NAME(m_voice[j].o2n2), j);
|
|
save_item(NAME(m_voice[j].o1n1), j);
|
|
save_item(NAME(m_voice[j].exbank), j);
|
|
}
|
|
|
|
}
|
|
|
|
void es5506_device::device_start()
|
|
{
|
|
es550x_device::device_start();
|
|
int channels = 1; /* 1 channel by default, for backward compatibility */
|
|
|
|
/* only override the number of channels if the value is in the valid range 1 .. 6 */
|
|
if (1 <= m_channels && m_channels <= 6)
|
|
channels = m_channels;
|
|
|
|
/* create the stream */
|
|
m_stream = machine().sound().stream_alloc(*this, 0, 2 * channels, clock() / (16*32));
|
|
|
|
/* initialize the regions */
|
|
m_region_base[0] = m_region_base[1] = m_region_base[2] = m_region_base[3] = nullptr;
|
|
if (m_region0)
|
|
{
|
|
memory_region *region0 = machine().root_device().memregion(m_region0);
|
|
if (region0 != nullptr)
|
|
{
|
|
m_region_base[0] = (u16 *)region0->base();
|
|
}
|
|
}
|
|
if (m_region1)
|
|
{
|
|
memory_region *region1 = machine().root_device().memregion(m_region1);
|
|
if (region1 != nullptr)
|
|
{
|
|
m_region_base[1] = (u16 *)region1->base();
|
|
}
|
|
}
|
|
if (m_region2)
|
|
{
|
|
memory_region *region2 = machine().root_device().memregion(m_region2);
|
|
if (region2 != nullptr)
|
|
{
|
|
m_region_base[2] = (u16 *)region2->base();
|
|
}
|
|
}
|
|
if (m_region3)
|
|
{
|
|
memory_region *region3 = machine().root_device().memregion(m_region3);
|
|
if (region3 != nullptr)
|
|
{
|
|
m_region_base[3] = (u16 *)region3->base();
|
|
}
|
|
}
|
|
|
|
/* initialize the rest of the structure */
|
|
m_channels = channels;
|
|
|
|
/* KT-76 assumes all voices are active on an ES5506 without setting them! */
|
|
m_active_voices = 31;
|
|
m_sample_rate = m_master_clock / (16 * (m_active_voices + 1));
|
|
m_stream->set_sample_rate(m_sample_rate);
|
|
|
|
/* init the voices */
|
|
// 21 bit integer and 11 bit fraction
|
|
m_accum_shift = ADDRESS_FRAC_BIT - ADDRESS_FRAC_BIT_ES5506;
|
|
m_accum_mask = (((((1 << 21) - 1) << ADDRESS_FRAC_BIT_ES5506) | ((1 << ADDRESS_FRAC_BIT_ES5506) - 1)) << m_accum_shift) | ((1 << m_accum_shift) - 1);
|
|
for (int j = 0; j < 32; j++)
|
|
{
|
|
m_voice[j].index = j;
|
|
m_voice[j].control = CONTROL_STOPMASK;
|
|
m_voice[j].lvol = 0xffff << VOLUME_SHIFT_ES5506; // 16 bit volume
|
|
m_voice[j].rvol = 0xffff << VOLUME_SHIFT_ES5506;
|
|
m_voice[j].exbank = 0;
|
|
}
|
|
|
|
/* register save */
|
|
save_item(NAME(m_write_latch));
|
|
save_item(NAME(m_read_latch));
|
|
|
|
save_item(NAME(m_wst));
|
|
save_item(NAME(m_wend));
|
|
save_item(NAME(m_lrend));
|
|
|
|
for (int j = 0; j < 32; j++)
|
|
{
|
|
save_item(NAME(m_voice[j].ecount), j);
|
|
save_item(NAME(m_voice[j].lvramp), j);
|
|
save_item(NAME(m_voice[j].rvramp), j);
|
|
save_item(NAME(m_voice[j].k2ramp), j);
|
|
save_item(NAME(m_voice[j].k1ramp), j);
|
|
save_item(NAME(m_voice[j].filtcount), j);
|
|
}
|
|
|
|
/* success */
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_clock_changed
|
|
//-------------------------------------------------
|
|
|
|
void es550x_device::device_clock_changed()
|
|
{
|
|
m_master_clock = clock();
|
|
m_sample_rate = m_master_clock / (16 * (m_active_voices + 1));
|
|
m_stream->set_sample_rate(m_sample_rate);
|
|
if (!m_sample_rate_changed_cb.isnull())
|
|
m_sample_rate_changed_cb(m_sample_rate);
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_reset - device-specific reset
|
|
//-------------------------------------------------
|
|
|
|
void es550x_device::device_reset()
|
|
{
|
|
}
|
|
|
|
void es5506_device::device_reset() // RESB for Reset input
|
|
{
|
|
m_active_voices = 0x1f; // 32 voice at reset
|
|
m_mode = 0x17; // Dual, Slave, BCLK_EN high, WCLK_EN high, LRCLK_EN high
|
|
notify_clock_changed();
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_stop - device-specific stop
|
|
//-------------------------------------------------
|
|
|
|
void es550x_device::device_stop()
|
|
{
|
|
#if ES5506_MAKE_WAVS
|
|
{
|
|
wav_close(m_wavraw);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
DEFINE_DEVICE_TYPE(ES5505, es5505_device, "es5505", "Ensoniq ES5505")
|
|
|
|
es5505_device::es5505_device(const machine_config &mconfig, const char *tag, device_t *owner, u32 clock)
|
|
: es550x_device(mconfig, ES5505, tag, owner, clock)
|
|
{
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// device_start - device-specific startup
|
|
//-------------------------------------------------
|
|
|
|
void es5505_device::device_start()
|
|
{
|
|
es550x_device::device_start();
|
|
int channels = 1; /* 1 channel by default, for backward compatibility */
|
|
|
|
/* only override the number of channels if the value is in the valid range 1 .. 4 */
|
|
if (1 <= m_channels && m_channels <= 4)
|
|
channels = m_channels;
|
|
|
|
/* create the stream */
|
|
m_stream = machine().sound().stream_alloc(*this, 0, 2 * channels, clock() / (16*32));
|
|
|
|
/* initialize the regions */
|
|
if (m_region0)
|
|
{
|
|
memory_region* region = machine().root_device().memregion(m_region0);
|
|
m_region_base[0] = region ? reinterpret_cast<u16 *>(region->base()) : nullptr;
|
|
}
|
|
if (m_region1)
|
|
{
|
|
memory_region* region = machine().root_device().memregion(m_region1);
|
|
m_region_base[1] = region ? reinterpret_cast<u16 *>(region->base()) : nullptr;
|
|
}
|
|
|
|
/* initialize the rest of the structure */
|
|
m_channels = channels;
|
|
|
|
/* init the voices */
|
|
// 20 bit integer and 9 bit fraction
|
|
m_accum_shift = ADDRESS_FRAC_BIT - ADDRESS_FRAC_BIT_ES5505;
|
|
m_accum_mask = (((((1 << 20) - 1) << ADDRESS_FRAC_BIT_ES5505) | ((1 << ADDRESS_FRAC_BIT_ES5505) - 1)) << m_accum_shift) | ((1 << m_accum_shift) - 1);
|
|
for (int j = 0; j < 32; j++)
|
|
{
|
|
m_voice[j].index = j;
|
|
m_voice[j].control = CONTROL_STOPMASK;
|
|
m_voice[j].lvol = 0xff << VOLUME_SHIFT_ES5505; // 8 bit volume
|
|
m_voice[j].rvol = 0xff << VOLUME_SHIFT_ES5505;
|
|
m_voice[j].exbank = 0;
|
|
}
|
|
|
|
/* success */
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
update_irq_state -- update the IRQ state
|
|
|
|
***********************************************************************************************/
|
|
|
|
|
|
void es550x_device::update_irq_state()
|
|
{
|
|
/* ES5505/6 irq line has been set high - inform the host */
|
|
if (!m_irq_cb.isnull())
|
|
m_irq_cb(1); /* IRQB set high */
|
|
}
|
|
|
|
void es550x_device::update_internal_irq_state()
|
|
{
|
|
/* Host (cpu) has just read the voice interrupt vector (voice IRQ ack).
|
|
|
|
Reset the voice vector to show the IRQB line is low (top bit set).
|
|
If we have any stacked interrupts (other voices waiting to be
|
|
processed - with their IRQ bit set) then they will be moved into
|
|
the vector next time the voice is processed. In emulation
|
|
terms they get updated next time generate_samples() is called.
|
|
*/
|
|
|
|
m_irqv = 0x80;
|
|
|
|
if (!m_irq_cb.isnull())
|
|
m_irq_cb(0); /* IRQB set low */
|
|
}
|
|
|
|
/**********************************************************************************************
|
|
|
|
compute_tables -- compute static tables
|
|
|
|
***********************************************************************************************/
|
|
|
|
void es550x_device::compute_tables()
|
|
{
|
|
/* allocate ulaw lookup table */
|
|
m_ulaw_lookup = make_unique_clear<s16[]>(1 << ULAW_MAXBITS);
|
|
|
|
/* generate ulaw lookup table */
|
|
for (int i = 0; i < (1 << ULAW_MAXBITS); i++)
|
|
{
|
|
u16 rawval = (i << (16 - ULAW_MAXBITS)) | (1 << (15 - ULAW_MAXBITS));
|
|
u8 exponent = rawval >> 13;
|
|
u32 mantissa = (rawval << 3) & 0xffff;
|
|
|
|
if (exponent == 0)
|
|
m_ulaw_lookup[i] = (s16)mantissa >> 7;
|
|
else
|
|
{
|
|
mantissa = (mantissa >> 1) | (~mantissa & 0x8000);
|
|
m_ulaw_lookup[i] = (s16)mantissa >> (7 - exponent);
|
|
}
|
|
}
|
|
|
|
const u32 volume_len = 1 << VOLUME_INTEGER_BIT;
|
|
/* allocate volume lookup table */
|
|
m_volume_lookup = make_unique_clear<u32[]>(volume_len);
|
|
|
|
/* generate volume lookup table */
|
|
const u8 exponent_bit = (VOLUME_INTEGER_BIT - 4);
|
|
const u32 mantissa_len = (1 << exponent_bit);
|
|
const u32 mantissa_mask = (mantissa_len - 1);
|
|
for (int i = 0; i < volume_len; i++)
|
|
{
|
|
u8 exponent = i >> exponent_bit;
|
|
u32 mantissa = (i & mantissa_mask) | mantissa_len;
|
|
|
|
m_volume_lookup[i] = (mantissa << 11) >> (20 - exponent);
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
interpolate -- interpolate between two samples
|
|
|
|
***********************************************************************************************/
|
|
|
|
inline s32 es550x_device::interpolate(s32 sample1, s32 sample2, u32 accum)
|
|
{
|
|
const u32 shifted = 1 << ADDRESS_FRAC_BIT;
|
|
const u32 mask = shifted - 1;
|
|
accum &= mask & m_accum_mask;
|
|
return (sample1 * (s32)(shifted - accum) +
|
|
sample2 * (s32)(accum)) >> ADDRESS_FRAC_BIT;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
apply_filters -- apply the 4-pole digital filter to the sample
|
|
|
|
***********************************************************************************************/
|
|
|
|
// apply lowpass/highpass result
|
|
static inline s32 apply_lowpass(s32 out, s32 cutoff, s32 in)
|
|
{
|
|
return ((s32)(cutoff >> FILTER_SHIFT) * (out - in) / (1 << FILTER_BIT)) + in;
|
|
}
|
|
|
|
static inline s32 apply_highpass(s32 out, s32 cutoff, s32 in, s32 prev)
|
|
{
|
|
return out - prev + ((s32)(cutoff >> FILTER_SHIFT) * in) / (1 << (FILTER_BIT + 1)) + in / 2;
|
|
}
|
|
|
|
// update poles from outputs
|
|
static inline void update_pole(s32 &pole, s32 sample)
|
|
{
|
|
pole = sample;
|
|
}
|
|
|
|
static inline void update_2_pole(s32 &prev, s32 &pole, s32 sample)
|
|
{
|
|
prev = pole;
|
|
pole = sample;
|
|
}
|
|
|
|
inline void es550x_device::apply_filters(es550x_voice *voice, s32 &sample)
|
|
{
|
|
/* pole 1 is always low-pass using K1 */
|
|
sample = apply_lowpass(sample, voice->k1, voice->o1n1);
|
|
update_pole(voice->o1n1, sample);
|
|
|
|
/* pole 2 is always low-pass using K1 */
|
|
sample = apply_lowpass(sample, voice->k1, voice->o2n1);
|
|
update_2_pole(voice->o2n2, voice->o2n1, sample);
|
|
|
|
/* remaining poles depend on the current filter setting */
|
|
switch (get_lp(voice->control))
|
|
{
|
|
case 0:
|
|
/* pole 3 is high-pass using K2 */
|
|
sample = apply_highpass(sample, voice->k2, voice->o3n1, voice->o2n2);
|
|
update_2_pole(voice->o3n2, voice->o3n1, sample);
|
|
|
|
/* pole 4 is high-pass using K2 */
|
|
sample = apply_highpass(sample, voice->k2, voice->o4n1, voice->o3n2);
|
|
update_pole(voice->o4n1, sample);
|
|
break;
|
|
|
|
case LP3:
|
|
/* pole 3 is low-pass using K1 */
|
|
sample = apply_lowpass(sample, voice->k1, voice->o3n1);
|
|
update_2_pole(voice->o3n2, voice->o3n1, sample);
|
|
|
|
/* pole 4 is high-pass using K2 */
|
|
sample = apply_highpass(sample, voice->k2, voice->o4n1, voice->o3n2);
|
|
update_pole(voice->o4n1, sample);
|
|
break;
|
|
|
|
case LP4:
|
|
/* pole 3 is low-pass using K2 */
|
|
sample = apply_lowpass(sample, voice->k2, voice->o3n1);
|
|
update_2_pole(voice->o3n2, voice->o3n1, sample);
|
|
|
|
/* pole 4 is low-pass using K2 */
|
|
sample = apply_lowpass(sample, voice->k2, voice->o4n1);
|
|
update_pole(voice->o4n1, sample);
|
|
break;
|
|
|
|
case LP3 | LP4:
|
|
/* pole 3 is low-pass using K1 */
|
|
sample = apply_lowpass(sample, voice->k1, voice->o3n1);
|
|
update_2_pole(voice->o3n2, voice->o3n1, sample);
|
|
|
|
/* pole 4 is low-pass using K2 */
|
|
sample = apply_lowpass(sample, voice->k2, voice->o4n1);
|
|
update_pole(voice->o4n1, sample);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
update_envelopes -- update the envelopes
|
|
|
|
***********************************************************************************************/
|
|
|
|
inline void es5506_device::update_envelopes(es550x_voice *voice)
|
|
{
|
|
/* decrement the envelope counter */
|
|
voice->ecount--;
|
|
|
|
/* ramp left volume */
|
|
if (voice->lvramp)
|
|
{
|
|
voice->lvol += (int8_t)voice->lvramp;
|
|
if ((s32)voice->lvol < 0) voice->lvol = 0;
|
|
else if (voice->lvol > 0xffff) voice->lvol = 0xffff;
|
|
}
|
|
|
|
/* ramp right volume */
|
|
if (voice->rvramp)
|
|
{
|
|
voice->rvol += (int8_t)voice->rvramp;
|
|
if ((s32)voice->rvol < 0) voice->rvol = 0;
|
|
else if (voice->rvol > 0xffff) voice->rvol = 0xffff;
|
|
}
|
|
|
|
/* ramp k1 filter constant */
|
|
if (voice->k1ramp && ((s32)voice->k1ramp >= 0 || !(voice->filtcount & 7)))
|
|
{
|
|
voice->k1 += (int8_t)voice->k1ramp;
|
|
if ((s32)voice->k1 < 0) voice->k1 = 0;
|
|
else if (voice->k1 > 0xffff) voice->k1 = 0xffff;
|
|
}
|
|
|
|
/* ramp k2 filter constant */
|
|
if (voice->k2ramp && ((s32)voice->k2ramp >= 0 || !(voice->filtcount & 7)))
|
|
{
|
|
voice->k2 += (int8_t)voice->k2ramp;
|
|
if ((s32)voice->k2 < 0) voice->k2 = 0;
|
|
else if (voice->k2 > 0xffff) voice->k2 = 0xffff;
|
|
}
|
|
|
|
/* update the filter constant counter */
|
|
voice->filtcount++;
|
|
|
|
}
|
|
|
|
inline void es5505_device::update_envelopes(es550x_voice *voice)
|
|
{
|
|
/* no envelopes in ES5505 */
|
|
voice->ecount = 0;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
check_for_end_forward
|
|
check_for_end_reverse -- check for loop end and loop appropriately
|
|
|
|
***********************************************************************************************/
|
|
|
|
inline void es5506_device::check_for_end_forward(es550x_voice *voice, u32 &accum)
|
|
{
|
|
/* are we past the end? */
|
|
if (accum > voice->end && !(voice->control & CONTROL_LEI))
|
|
{
|
|
/* generate interrupt if required */
|
|
if (voice->control & CONTROL_IRQE)
|
|
voice->control |= CONTROL_IRQ;
|
|
|
|
/* handle the different types of looping */
|
|
switch (voice->control & CONTROL_LOOPMASK)
|
|
{
|
|
/* non-looping */
|
|
case 0:
|
|
voice->control |= CONTROL_STOP0;
|
|
break;
|
|
|
|
/* uni-directional looping */
|
|
case CONTROL_LPE:
|
|
accum = (voice->start + (accum - voice->end)) & m_accum_mask;
|
|
break;
|
|
|
|
/* trans-wave looping */
|
|
case CONTROL_BLE:
|
|
accum = (voice->start + (accum - voice->end)) & m_accum_mask;
|
|
voice->control = (voice->control & ~CONTROL_LOOPMASK) | CONTROL_LEI;
|
|
break;
|
|
|
|
/* bi-directional looping */
|
|
case CONTROL_LPE | CONTROL_BLE:
|
|
accum = (voice->end - (accum - voice->end)) & m_accum_mask;
|
|
voice->control ^= CONTROL_DIR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void es5506_device::check_for_end_reverse(es550x_voice *voice, u32 &accum)
|
|
{
|
|
/* are we past the end? */
|
|
if (accum < voice->start && !(voice->control & CONTROL_LEI))
|
|
{
|
|
/* generate interrupt if required */
|
|
if (voice->control & CONTROL_IRQE)
|
|
voice->control |= CONTROL_IRQ;
|
|
|
|
/* handle the different types of looping */
|
|
switch (voice->control & CONTROL_LOOPMASK)
|
|
{
|
|
/* non-looping */
|
|
case 0:
|
|
voice->control |= CONTROL_STOP0;
|
|
break;
|
|
|
|
/* uni-directional looping */
|
|
case CONTROL_LPE:
|
|
accum = (voice->end - (voice->start - accum)) & m_accum_mask;
|
|
break;
|
|
|
|
/* trans-wave looping */
|
|
case CONTROL_BLE:
|
|
accum = (voice->end - (voice->start - accum)) & m_accum_mask;
|
|
voice->control = (voice->control & ~CONTROL_LOOPMASK) | CONTROL_LEI;
|
|
break;
|
|
|
|
/* bi-directional looping */
|
|
case CONTROL_LPE | CONTROL_BLE:
|
|
accum = (voice->start + (voice->start - accum)) & m_accum_mask;
|
|
voice->control ^= CONTROL_DIR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// ES5505 : BLE is ignored when LPE = 0
|
|
inline void es5505_device::check_for_end_forward(es550x_voice *voice, u32 &accum)
|
|
{
|
|
/* are we past the end? */
|
|
if (accum > voice->end)
|
|
{
|
|
/* generate interrupt if required */
|
|
if (voice->control & CONTROL_IRQE)
|
|
voice->control |= CONTROL_IRQ;
|
|
|
|
/* handle the different types of looping */
|
|
switch (voice->control & CONTROL_LOOPMASK)
|
|
{
|
|
/* non-looping */
|
|
case 0:
|
|
case CONTROL_BLE:
|
|
voice->control |= CONTROL_STOP0;
|
|
break;
|
|
|
|
/* uni-directional looping */
|
|
case CONTROL_LPE:
|
|
accum = (voice->start + (accum - voice->end)) & m_accum_mask;
|
|
break;
|
|
|
|
/* bi-directional looping */
|
|
case CONTROL_LPE | CONTROL_BLE:
|
|
accum = (voice->end - (accum - voice->end)) & m_accum_mask;
|
|
voice->control ^= CONTROL_DIR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
inline void es5505_device::check_for_end_reverse(es550x_voice *voice, u32 &accum)
|
|
{
|
|
/* are we past the end? */
|
|
if (accum < voice->start)
|
|
{
|
|
/* generate interrupt if required */
|
|
if (voice->control & CONTROL_IRQE)
|
|
voice->control |= CONTROL_IRQ;
|
|
|
|
/* handle the different types of looping */
|
|
switch (voice->control & CONTROL_LOOPMASK)
|
|
{
|
|
/* non-looping */
|
|
case 0:
|
|
case CONTROL_BLE:
|
|
voice->control |= CONTROL_STOP0;
|
|
break;
|
|
|
|
/* uni-directional looping */
|
|
case CONTROL_LPE:
|
|
accum = (voice->end - (voice->start - accum)) & m_accum_mask;
|
|
break;
|
|
|
|
/* bi-directional looping */
|
|
case CONTROL_LPE | CONTROL_BLE:
|
|
accum = (voice->start + (voice->start - accum)) & m_accum_mask;
|
|
voice->control ^= CONTROL_DIR;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
generate_dummy -- generate nothing, just apply envelopes
|
|
|
|
***********************************************************************************************/
|
|
|
|
void es550x_device::generate_dummy(es550x_voice *voice, u16 *base, s32 *lbuffer, s32 *rbuffer, int samples)
|
|
{
|
|
const u32 freqcount = voice->freqcount;
|
|
u32 accum = voice->accum & m_accum_mask;
|
|
|
|
/* outer loop, in case we switch directions */
|
|
if (!(voice->control & CONTROL_STOPMASK))
|
|
{
|
|
/* two cases: first case is forward direction */
|
|
if (!(voice->control & CONTROL_DIR))
|
|
{
|
|
/* fetch two samples */
|
|
accum = (accum + freqcount) & m_accum_mask;
|
|
|
|
/* update filters/volumes */
|
|
if (voice->ecount != 0)
|
|
update_envelopes(voice);
|
|
|
|
/* check for loop end */
|
|
check_for_end_forward(voice, accum);
|
|
}
|
|
|
|
/* two cases: second case is backward direction */
|
|
else
|
|
{
|
|
/* fetch two samples */
|
|
accum = (accum - freqcount) & m_accum_mask;
|
|
|
|
/* update filters/volumes */
|
|
if (voice->ecount != 0)
|
|
update_envelopes(voice);
|
|
|
|
/* check for loop end */
|
|
check_for_end_reverse(voice, accum);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* if we stopped, process any additional envelope */
|
|
if (voice->ecount != 0)
|
|
update_envelopes(voice);
|
|
}
|
|
|
|
voice->accum = accum;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
generate_ulaw -- general u-law decoding routine
|
|
|
|
***********************************************************************************************/
|
|
|
|
void es550x_device::generate_ulaw(es550x_voice *voice, u16 *base, s32 *lbuffer, s32 *rbuffer, int samples)
|
|
{
|
|
const u32 freqcount = voice->freqcount;
|
|
u32 accum = voice->accum & m_accum_mask;
|
|
s32 lvol = get_volume(voice->lvol);
|
|
s32 rvol = get_volume(voice->rvol);
|
|
|
|
/* pre-add the bank offset */
|
|
base += voice->exbank;
|
|
|
|
/* outer loop, in case we switch directions */
|
|
if (!(voice->control & CONTROL_STOPMASK))
|
|
{
|
|
/* two cases: first case is forward direction */
|
|
if (!(voice->control & CONTROL_DIR))
|
|
{
|
|
/* fetch two samples */
|
|
s32 val1 = base[get_integer_addr(accum)];
|
|
s32 val2 = base[get_integer_addr(accum, 1)];
|
|
|
|
/* decompress u-law */
|
|
val1 = m_ulaw_lookup[val1 >> (16 - ULAW_MAXBITS)];
|
|
val2 = m_ulaw_lookup[val2 >> (16 - ULAW_MAXBITS)];
|
|
|
|
/* interpolate */
|
|
val1 = interpolate(val1, val2, accum);
|
|
accum = (accum + freqcount) & m_accum_mask;
|
|
|
|
/* apply filters */
|
|
apply_filters(voice, val1);
|
|
|
|
/* update filters/volumes */
|
|
if (voice->ecount != 0)
|
|
{
|
|
update_envelopes(voice);
|
|
lvol = get_volume(voice->lvol);
|
|
rvol = get_volume(voice->rvol);
|
|
}
|
|
|
|
/* apply volumes and add */
|
|
*lbuffer += (val1 * lvol) >> VOLUME_ACC_SHIFT;
|
|
*rbuffer += (val1 * rvol) >> VOLUME_ACC_SHIFT;
|
|
|
|
/* check for loop end */
|
|
check_for_end_forward(voice, accum);
|
|
}
|
|
|
|
/* two cases: second case is backward direction */
|
|
else
|
|
{
|
|
/* fetch two samples */
|
|
s32 val1 = base[get_integer_addr(accum)];
|
|
s32 val2 = base[get_integer_addr(accum, 1)];
|
|
|
|
/* decompress u-law */
|
|
val1 = m_ulaw_lookup[val1 >> (16 - ULAW_MAXBITS)];
|
|
val2 = m_ulaw_lookup[val2 >> (16 - ULAW_MAXBITS)];
|
|
|
|
/* interpolate */
|
|
val1 = interpolate(val1, val2, accum);
|
|
accum = (accum - freqcount) & m_accum_mask;
|
|
|
|
/* apply filters */
|
|
apply_filters(voice, val1);
|
|
|
|
/* update filters/volumes */
|
|
if (voice->ecount != 0)
|
|
{
|
|
update_envelopes(voice);
|
|
lvol = get_volume(voice->lvol);
|
|
rvol = get_volume(voice->rvol);
|
|
}
|
|
|
|
/* apply volumes and add */
|
|
*lbuffer += (val1 * lvol) >> VOLUME_ACC_SHIFT;
|
|
*rbuffer += (val1 * rvol) >> VOLUME_ACC_SHIFT;
|
|
|
|
/* check for loop end */
|
|
check_for_end_reverse(voice, accum);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* if we stopped, process any additional envelope */
|
|
if (voice->ecount != 0)
|
|
update_envelopes(voice);
|
|
}
|
|
|
|
voice->accum = accum;
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
generate_pcm -- general PCM decoding routine
|
|
|
|
***********************************************************************************************/
|
|
|
|
void es550x_device::generate_pcm(es550x_voice *voice, u16 *base, s32 *lbuffer, s32 *rbuffer, int samples)
|
|
{
|
|
const u32 freqcount = voice->freqcount;
|
|
u32 accum = voice->accum & m_accum_mask;
|
|
s32 lvol = get_volume(voice->lvol);
|
|
s32 rvol = get_volume(voice->rvol);
|
|
|
|
/* pre-add the bank offset */
|
|
base += voice->exbank;
|
|
|
|
/* outer loop, in case we switch directions */
|
|
if (!(voice->control & CONTROL_STOPMASK))
|
|
{
|
|
/* two cases: first case is forward direction */
|
|
if (!(voice->control & CONTROL_DIR))
|
|
{
|
|
/* fetch two samples */
|
|
s32 val1 = (s16)base[get_integer_addr(accum)];
|
|
s32 val2 = (s16)base[get_integer_addr(accum, 1)];
|
|
|
|
/* interpolate */
|
|
val1 = interpolate(val1, val2, accum);
|
|
accum = (accum + freqcount) & m_accum_mask;
|
|
|
|
/* apply filters */
|
|
apply_filters(voice, val1);
|
|
|
|
/* update filters/volumes */
|
|
if (voice->ecount != 0)
|
|
{
|
|
update_envelopes(voice);
|
|
lvol = get_volume(voice->lvol);
|
|
rvol = get_volume(voice->rvol);
|
|
}
|
|
|
|
/* apply volumes and add */
|
|
*lbuffer += (val1 * lvol) >> VOLUME_ACC_SHIFT;
|
|
*rbuffer += (val1 * rvol) >> VOLUME_ACC_SHIFT;
|
|
|
|
/* check for loop end */
|
|
check_for_end_forward(voice, accum);
|
|
}
|
|
|
|
/* two cases: second case is backward direction */
|
|
else
|
|
{
|
|
/* fetch two samples */
|
|
s32 val1 = (s16)base[get_integer_addr(accum)];
|
|
s32 val2 = (s16)base[get_integer_addr(accum, 1)];
|
|
|
|
/* interpolate */
|
|
val1 = interpolate(val1, val2, accum);
|
|
accum = (accum - freqcount) & m_accum_mask;
|
|
|
|
/* apply filters */
|
|
apply_filters(voice, val1);
|
|
|
|
/* update filters/volumes */
|
|
if (voice->ecount != 0)
|
|
{
|
|
update_envelopes(voice);
|
|
lvol = get_volume(voice->lvol);
|
|
rvol = get_volume(voice->rvol);
|
|
}
|
|
|
|
/* apply volumes and add */
|
|
*lbuffer += (val1 * lvol) >> VOLUME_ACC_SHIFT;
|
|
*rbuffer += (val1 * rvol) >> VOLUME_ACC_SHIFT;
|
|
|
|
/* check for loop end */
|
|
check_for_end_reverse(voice, accum);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* if we stopped, process any additional envelope */
|
|
if (voice->ecount != 0)
|
|
update_envelopes(voice);
|
|
}
|
|
|
|
voice->accum = accum;
|
|
}
|
|
|
|
/**********************************************************************************************
|
|
|
|
generate_irq -- general interrupt handling routine
|
|
|
|
***********************************************************************************************/
|
|
|
|
inline void es550x_device::generate_irq(es550x_voice *voice, int v)
|
|
{
|
|
/* does this voice have it's IRQ bit raised? */
|
|
if (voice->control & CONTROL_IRQ)
|
|
{
|
|
LOG("es5506: IRQ raised on voice %d!!\n",v);
|
|
|
|
/* only update voice vector if existing IRQ is acked by host */
|
|
if (m_irqv & 0x80)
|
|
{
|
|
/* latch voice number into vector, and set high bit low */
|
|
m_irqv = v & 0x1f;
|
|
|
|
/* take down IRQ bit on voice */
|
|
voice->control &= ~CONTROL_IRQ;
|
|
|
|
/* inform host of irq */
|
|
update_irq_state();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
generate_samples -- tell each voice to generate samples
|
|
|
|
***********************************************************************************************/
|
|
|
|
void es5506_device::generate_samples(s32 **outputs, int offset, int samples)
|
|
{
|
|
/* skip if nothing to do */
|
|
if (!samples)
|
|
return;
|
|
|
|
/* clear out the accumulators */
|
|
for (int i = 0; i < m_channels << 1; i++)
|
|
{
|
|
memset(outputs[i] + offset, 0, sizeof(s32) * samples);
|
|
}
|
|
|
|
/* loop while we still have samples to generate */
|
|
while (samples)
|
|
{
|
|
/* loop over voices */
|
|
for (int v = 0; v <= m_active_voices; v++)
|
|
{
|
|
es550x_voice *voice = &m_voice[v];
|
|
u16 *base = m_region_base[get_bank(voice->control)];
|
|
|
|
/* special case: if end == start, stop the voice */
|
|
if (voice->start == voice->end)
|
|
voice->control |= CONTROL_STOP0;
|
|
|
|
const int voice_channel = get_ca(voice->control);
|
|
const int channel = voice_channel % m_channels;
|
|
const int l = channel << 1;
|
|
const int r = l + 1;
|
|
s32 *left = outputs[l] + offset;
|
|
s32 *right = outputs[r] + offset;
|
|
|
|
/* generate from the appropriate source */
|
|
if (!base)
|
|
{
|
|
LOG("es5506: nullptr region base %d\n",get_bank(voice->control));
|
|
generate_dummy(voice, base, left, right, samples);
|
|
}
|
|
else if (voice->control & CONTROL_CMPD)
|
|
generate_ulaw(voice, base, left, right, samples);
|
|
else
|
|
generate_pcm(voice, base, left, right, samples);
|
|
|
|
/* does this voice have it's IRQ bit raised? */
|
|
generate_irq(voice, v);
|
|
}
|
|
offset++;
|
|
samples--;
|
|
}
|
|
}
|
|
|
|
void es5505_device::generate_samples(s32 **outputs, int offset, int samples)
|
|
{
|
|
/* skip if nothing to do */
|
|
if (!samples)
|
|
return;
|
|
|
|
/* clear out the accumulators */
|
|
for (int i = 0; i < m_channels << 1; i++)
|
|
{
|
|
memset(outputs[i] + offset, 0, sizeof(s32) * samples);
|
|
}
|
|
|
|
/* loop while we still have samples to generate */
|
|
while (samples)
|
|
{
|
|
/* loop over voices */
|
|
for (int v = 0; v <= m_active_voices; v++)
|
|
{
|
|
es550x_voice *voice = &m_voice[v];
|
|
u16 *base = m_region_base[get_bank(voice->control)];
|
|
|
|
// This special case does not appear to match the behaviour observed in the es5505 in
|
|
// actual Ensoniq synthesizers: those, it turns out, do set loop start and end to the
|
|
// same value, and expect the voice to keep running. Examples can be found among the
|
|
// transwaves on the VFX / SD-1 series of synthesizers.
|
|
#if 0
|
|
/* special case: if end == start, stop the voice */
|
|
if (voice->start == voice->end)
|
|
voice->control |= CONTROL_STOP0;
|
|
#endif
|
|
|
|
const int voice_channel = get_ca(voice->control);
|
|
const int channel = voice_channel % m_channels;
|
|
const int l = channel << 1;
|
|
const int r = l + 1;
|
|
s32 *left = outputs[l] + offset;
|
|
s32 *right = outputs[r] + offset;
|
|
|
|
/* generate from the appropriate source */
|
|
if (!base)
|
|
{
|
|
LOG("es5506: nullptr region base %d\n",get_bank(voice->control));
|
|
generate_dummy(voice, base, left, right, samples);
|
|
}
|
|
// no compressed sample support
|
|
else
|
|
generate_pcm(voice, base, left, right, samples);
|
|
|
|
/* does this voice have it's IRQ bit raised? */
|
|
generate_irq(voice, v);
|
|
}
|
|
offset++;
|
|
samples--;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
reg_write -- handle a write to the selected ES5506 register
|
|
|
|
***********************************************************************************************/
|
|
|
|
inline void es5506_device::reg_write_low(es550x_voice *voice, offs_t offset, u32 data)
|
|
{
|
|
switch (offset)
|
|
{
|
|
case 0x00/8: /* CR */
|
|
voice->control = data & 0xffff;
|
|
LOG("voice %d, control=%04x\n", m_current_page & 0x1f, voice->control);
|
|
break;
|
|
|
|
case 0x08/8: /* FC */
|
|
voice->freqcount = get_accum_shifted_val(data & 0x1ffff);
|
|
LOG("voice %d, freq count=%08x\n", m_current_page & 0x1f, get_accum_res(voice->freqcount));
|
|
break;
|
|
|
|
case 0x10/8: /* LVOL */
|
|
voice->lvol = data & 0xffff; // low 4 bit is used for finer envelope control
|
|
LOG("voice %d, left vol=%04x\n", m_current_page & 0x1f, voice->lvol);
|
|
break;
|
|
|
|
case 0x18/8: /* LVRAMP */
|
|
voice->lvramp = (data & 0xff00) >> 8;
|
|
LOG("voice %d, left vol ramp=%04x\n", m_current_page & 0x1f, voice->lvramp);
|
|
break;
|
|
|
|
case 0x20/8: /* RVOL */
|
|
voice->rvol = data & 0xffff; // low 4 bit is used for finer envelope control
|
|
LOG("voice %d, right vol=%04x\n", m_current_page & 0x1f, voice->rvol);
|
|
break;
|
|
|
|
case 0x28/8: /* RVRAMP */
|
|
voice->rvramp = (data & 0xff00) >> 8;
|
|
LOG("voice %d, right vol ramp=%04x\n", m_current_page & 0x1f, voice->rvramp);
|
|
break;
|
|
|
|
case 0x30/8: /* ECOUNT */
|
|
voice->ecount = data & 0x1ff;
|
|
voice->filtcount = 0;
|
|
LOG("voice %d, envelope count=%04x\n", m_current_page & 0x1f, voice->ecount);
|
|
break;
|
|
|
|
case 0x38/8: /* K2 */
|
|
voice->k2 = data & 0xffff; // low 4 bit is used for finer envelope control
|
|
LOG("voice %d, K2=%04x\n", m_current_page & 0x1f, voice->k2);
|
|
break;
|
|
|
|
case 0x40/8: /* K2RAMP */
|
|
voice->k2ramp = ((data & 0xff00) >> 8) | ((data & 0x0001) << 31);
|
|
LOG("voice %d, K2 ramp=%04x\n", m_current_page & 0x1f, voice->k2ramp);
|
|
break;
|
|
|
|
case 0x48/8: /* K1 */
|
|
voice->k1 = data & 0xffff; // low 4 bit is used for finer envelope control
|
|
LOG("voice %d, K1=%04x\n", m_current_page & 0x1f, voice->k1);
|
|
break;
|
|
|
|
case 0x50/8: /* K1RAMP */
|
|
voice->k1ramp = ((data & 0xff00) >> 8) | ((data & 0x0001) << 31);
|
|
LOG("voice %d, K1 ramp=%04x\n", m_current_page & 0x1f, voice->k1ramp);
|
|
break;
|
|
|
|
case 0x58/8: /* ACTV */
|
|
{
|
|
m_active_voices = data & 0x1f;
|
|
m_sample_rate = m_master_clock / (16 * (m_active_voices + 1));
|
|
m_stream->set_sample_rate(m_sample_rate);
|
|
if (!m_sample_rate_changed_cb.isnull())
|
|
m_sample_rate_changed_cb(m_sample_rate);
|
|
|
|
LOG("active voices=%d, sample_rate=%d\n", m_active_voices, m_sample_rate);
|
|
break;
|
|
}
|
|
|
|
case 0x60/8: /* MODE */
|
|
// [4:3] = 00 : Single, Master, Early address mode
|
|
// [4:3] = 01 : Single, Master, Normal address mode
|
|
// [4:3] = 10 : Dual, Slave, Normal address mode
|
|
// [4:3] = 11 : Dual, Master, Normal address mode
|
|
m_mode = data & 0x1f; // MODE1[4], MODE0[3], BCLK_EN[2], WCLK_EN[1], LRCLK_EN[0]
|
|
break;
|
|
|
|
case 0x68/8: /* PAR - read only */
|
|
case 0x70/8: /* IRQV - read only */
|
|
break;
|
|
|
|
case 0x78/8: /* PAGE */
|
|
m_current_page = data & 0x7f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline void es5506_device::reg_write_high(es550x_voice *voice, offs_t offset, u32 data)
|
|
{
|
|
switch (offset)
|
|
{
|
|
case 0x00/8: /* CR */
|
|
voice->control = data & 0xffff;
|
|
LOG("voice %d, control=%04x\n", m_current_page & 0x1f, voice->control);
|
|
break;
|
|
|
|
case 0x08/8: /* START */
|
|
voice->start = get_accum_shifted_val(data & 0xfffff800);
|
|
LOG("voice %d, loop start=%08x\n", m_current_page & 0x1f, get_accum_res(voice->start));
|
|
break;
|
|
|
|
case 0x10/8: /* END */
|
|
voice->end = get_accum_shifted_val(data & 0xffffff80);
|
|
LOG("voice %d, loop end=%08x\n", m_current_page & 0x1f, get_accum_res(voice->end));
|
|
break;
|
|
|
|
case 0x18/8: /* ACCUM */
|
|
voice->accum = get_accum_shifted_val(data);
|
|
LOG("voice %d, accum=%08x\n", m_current_page & 0x1f, get_accum_res(voice->accum));
|
|
break;
|
|
|
|
case 0x20/8: /* O4(n-1) */
|
|
voice->o4n1 = (s32)(data << 14) >> 14;
|
|
LOG("voice %d, O4(n-1)=%05x\n", m_current_page & 0x1f, voice->o4n1 & 0x3ffff);
|
|
break;
|
|
|
|
case 0x28/8: /* O3(n-1) */
|
|
voice->o3n1 = (s32)(data << 14) >> 14;
|
|
LOG("voice %d, O3(n-1)=%05x\n", m_current_page & 0x1f, voice->o3n1 & 0x3ffff);
|
|
break;
|
|
|
|
case 0x30/8: /* O3(n-2) */
|
|
voice->o3n2 = (s32)(data << 14) >> 14;
|
|
LOG("voice %d, O3(n-2)=%05x\n", m_current_page & 0x1f, voice->o3n2 & 0x3ffff);
|
|
break;
|
|
|
|
case 0x38/8: /* O2(n-1) */
|
|
voice->o2n1 = (s32)(data << 14) >> 14;
|
|
LOG("voice %d, O2(n-1)=%05x\n", m_current_page & 0x1f, voice->o2n1 & 0x3ffff);
|
|
break;
|
|
|
|
case 0x40/8: /* O2(n-2) */
|
|
voice->o2n2 = (s32)(data << 14) >> 14;
|
|
LOG("voice %d, O2(n-2)=%05x\n", m_current_page & 0x1f, voice->o2n2 & 0x3ffff);
|
|
break;
|
|
|
|
case 0x48/8: /* O1(n-1) */
|
|
voice->o1n1 = (s32)(data << 14) >> 14;
|
|
LOG("voice %d, O1(n-1)=%05x\n", m_current_page & 0x1f, voice->o1n1 & 0x3ffff);
|
|
break;
|
|
|
|
case 0x50/8: /* W_ST */
|
|
m_wst = data & 0x7f;
|
|
break;
|
|
|
|
case 0x58/8: /* W_END */
|
|
m_wend = data & 0x7f;
|
|
break;
|
|
|
|
case 0x60/8: /* LR_END */
|
|
m_lrend = data & 0x7f;
|
|
break;
|
|
|
|
case 0x68/8: /* PAR - read only */
|
|
case 0x70/8: /* IRQV - read only */
|
|
break;
|
|
|
|
case 0x78/8: /* PAGE */
|
|
m_current_page = data & 0x7f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
inline void es5506_device::reg_write_test(es550x_voice *voice, offs_t offset, u32 data)
|
|
{
|
|
switch (offset)
|
|
{
|
|
case 0x00/8: /* CHANNEL 0 LEFT */
|
|
LOG("Channel 0 left test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x08/8: /* CHANNEL 0 RIGHT */
|
|
LOG("Channel 0 right test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x10/8: /* CHANNEL 1 LEFT */
|
|
LOG("Channel 1 left test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x18/8: /* CHANNEL 1 RIGHT */
|
|
LOG("Channel 1 right test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x20/8: /* CHANNEL 2 LEFT */
|
|
LOG("Channel 2 left test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x28/8: /* CHANNEL 2 RIGHT */
|
|
LOG("Channel 2 right test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x30/8: /* CHANNEL 3 LEFT */
|
|
LOG("Channel 3 left test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x38/8: /* CHANNEL 3 RIGHT */
|
|
LOG("Channel 3 right test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x40/8: /* CHANNEL 4 LEFT */
|
|
LOG("Channel 4 left test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x48/8: /* CHANNEL 4 RIGHT */
|
|
LOG("Channel 4 right test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x50/8: /* CHANNEL 5 LEFT */
|
|
LOG("Channel 5 left test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x58/8: /* CHANNEL 6 RIGHT */
|
|
LOG("Channel 5 right test write %08x\n", data);
|
|
break;
|
|
|
|
case 0x60/8: /* EMPTY */
|
|
LOG("Test write EMPTY %08x\n", data);
|
|
break;
|
|
|
|
case 0x68/8: /* PAR - read only */
|
|
case 0x70/8: /* IRQV - read only */
|
|
break;
|
|
|
|
case 0x78/8: /* PAGE */
|
|
m_current_page = data & 0x7f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
WRITE8_MEMBER(es5506_device::write)
|
|
{
|
|
es550x_voice *voice = &m_voice[m_current_page & 0x1f];
|
|
int shift = 8 * (offset & 3);
|
|
|
|
/* accumulate the data */
|
|
m_write_latch = (m_write_latch & ~(0xff000000 >> shift)) | (data << (24 - shift));
|
|
|
|
/* wait for a write to complete */
|
|
if (shift != 24)
|
|
return;
|
|
|
|
/* force an update */
|
|
m_stream->update();
|
|
|
|
/* switch off the page and register */
|
|
if (m_current_page < 0x20)
|
|
reg_write_low(voice, offset / 4, m_write_latch);
|
|
else if (m_current_page < 0x40)
|
|
reg_write_high(voice, offset / 4, m_write_latch);
|
|
else
|
|
reg_write_test(voice, offset / 4, m_write_latch);
|
|
|
|
/* clear the write latch when done */
|
|
m_write_latch = 0;
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
reg_read -- read from the specified ES5506 register
|
|
|
|
***********************************************************************************************/
|
|
|
|
inline u32 es5506_device::reg_read_low(es550x_voice *voice, offs_t offset)
|
|
{
|
|
u32 result = 0;
|
|
|
|
switch (offset)
|
|
{
|
|
case 0x00/8: /* CR */
|
|
result = voice->control;
|
|
break;
|
|
|
|
case 0x08/8: /* FC */
|
|
result = get_accum_res(voice->freqcount);
|
|
break;
|
|
|
|
case 0x10/8: /* LVOL */
|
|
result = voice->lvol;
|
|
break;
|
|
|
|
case 0x18/8: /* LVRAMP */
|
|
result = voice->lvramp << 8;
|
|
break;
|
|
|
|
case 0x20/8: /* RVOL */
|
|
result = voice->rvol;
|
|
break;
|
|
|
|
case 0x28/8: /* RVRAMP */
|
|
result = voice->rvramp << 8;
|
|
break;
|
|
|
|
case 0x30/8: /* ECOUNT */
|
|
result = voice->ecount;
|
|
break;
|
|
|
|
case 0x38/8: /* K2 */
|
|
result = voice->k2;
|
|
break;
|
|
|
|
case 0x40/8: /* K2RAMP */
|
|
result = (voice->k2ramp << 8) | (voice->k2ramp >> 31);
|
|
break;
|
|
|
|
case 0x48/8: /* K1 */
|
|
result = voice->k1;
|
|
break;
|
|
|
|
case 0x50/8: /* K1RAMP */
|
|
result = (voice->k1ramp << 8) | (voice->k1ramp >> 31);
|
|
break;
|
|
|
|
case 0x58/8: /* ACTV */
|
|
result = m_active_voices;
|
|
break;
|
|
|
|
case 0x60/8: /* MODE */
|
|
result = m_mode;
|
|
break;
|
|
|
|
case 0x68/8: /* PAR */
|
|
if (!m_read_port_cb.isnull())
|
|
result = m_read_port_cb(0) & 0x3ff; // 10 bit, 9:0
|
|
break;
|
|
|
|
case 0x70/8: /* IRQV */
|
|
result = m_irqv;
|
|
if (!machine().side_effects_disabled())
|
|
update_internal_irq_state();
|
|
break;
|
|
|
|
case 0x78/8: /* PAGE */
|
|
result = m_current_page;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
inline u32 es5506_device::reg_read_high(es550x_voice *voice, offs_t offset)
|
|
{
|
|
u32 result = 0;
|
|
|
|
switch (offset)
|
|
{
|
|
case 0x00/8: /* CR */
|
|
result = voice->control;
|
|
break;
|
|
|
|
case 0x08/8: /* START */
|
|
result = get_accum_res(voice->start);
|
|
break;
|
|
|
|
case 0x10/8: /* END */
|
|
result = get_accum_res(voice->end);
|
|
break;
|
|
|
|
case 0x18/8: /* ACCUM */
|
|
result = get_accum_res(voice->accum);
|
|
break;
|
|
|
|
case 0x20/8: /* O4(n-1) */
|
|
result = voice->o4n1 & 0x3ffff;
|
|
break;
|
|
|
|
case 0x28/8: /* O3(n-1) */
|
|
result = voice->o3n1 & 0x3ffff;
|
|
break;
|
|
|
|
case 0x30/8: /* O3(n-2) */
|
|
result = voice->o3n2 & 0x3ffff;
|
|
break;
|
|
|
|
case 0x38/8: /* O2(n-1) */
|
|
result = voice->o2n1 & 0x3ffff;
|
|
break;
|
|
|
|
case 0x40/8: /* O2(n-2) */
|
|
result = voice->o2n2 & 0x3ffff;
|
|
break;
|
|
|
|
case 0x48/8: /* O1(n-1) */
|
|
result = voice->o1n1 & 0x3ffff;
|
|
break;
|
|
|
|
case 0x50/8: /* W_ST */
|
|
result = m_wst;
|
|
break;
|
|
|
|
case 0x58/8: /* W_END */
|
|
result = m_wend;
|
|
break;
|
|
|
|
case 0x60/8: /* LR_END */
|
|
result = m_lrend;
|
|
break;
|
|
|
|
case 0x68/8: /* PAR */
|
|
if (!m_read_port_cb.isnull())
|
|
result = m_read_port_cb(0) & 0x3ff; // 10 bit, 9:0
|
|
break;
|
|
|
|
case 0x70/8: /* IRQV */
|
|
result = m_irqv;
|
|
if (!machine().side_effects_disabled())
|
|
update_internal_irq_state();
|
|
break;
|
|
|
|
case 0x78/8: /* PAGE */
|
|
result = m_current_page;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
inline u32 es5506_device::reg_read_test(es550x_voice *voice, offs_t offset)
|
|
{
|
|
u32 result = 0;
|
|
|
|
switch (offset)
|
|
{
|
|
case 0x68/8: /* PAR */
|
|
if (!m_read_port_cb.isnull())
|
|
result = m_read_port_cb(0) & 0x3ff; // 10 bit, 9:0
|
|
break;
|
|
|
|
case 0x70/8: /* IRQV */
|
|
result = m_irqv;
|
|
break;
|
|
|
|
case 0x78/8: /* PAGE */
|
|
result = m_current_page;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
READ8_MEMBER(es5506_device::read)
|
|
{
|
|
es550x_voice *voice = &m_voice[m_current_page & 0x1f];
|
|
int shift = 8 * (offset & 3);
|
|
|
|
/* only read on offset 0 */
|
|
if (shift != 0)
|
|
return m_read_latch >> (24 - shift);
|
|
|
|
LOG("read from %02x/%02x -> ", m_current_page, offset / 4 * 8);
|
|
|
|
/* force an update */
|
|
m_stream->update();
|
|
|
|
/* switch off the page and register */
|
|
if (m_current_page < 0x20)
|
|
m_read_latch = reg_read_low(voice, offset / 4);
|
|
else if (m_current_page < 0x40)
|
|
m_read_latch = reg_read_high(voice, offset / 4);
|
|
else
|
|
m_read_latch = reg_read_test(voice, offset / 4);
|
|
|
|
LOG("%08x\n", m_read_latch);
|
|
|
|
/* return the high byte */
|
|
return m_read_latch >> 24;
|
|
}
|
|
|
|
|
|
|
|
void es5506_device::voice_bank_w(int voice, int bank)
|
|
{
|
|
m_voice[voice].exbank=bank;
|
|
}
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
reg_write -- handle a write to the selected ES5505 register
|
|
|
|
***********************************************************************************************/
|
|
|
|
inline void es5505_device::reg_write_low(es550x_voice *voice, offs_t offset, u16 data, u16 mem_mask)
|
|
{
|
|
switch (offset)
|
|
{
|
|
case 0x00: /* CR */
|
|
voice->control |= 0xf000; // bit 15-12 always 1
|
|
if (ACCESSING_BITS_0_7)
|
|
{
|
|
#if RAINE_CHECK
|
|
voice->control &= ~(CONTROL_STOPMASK | CONTROL_LOOPMASK | CONTROL_DIR);
|
|
#else
|
|
voice->control &= ~0x00ff;
|
|
#endif
|
|
voice->control |= (data & 0x00ff);
|
|
}
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->control = (voice->control & ~0x0f00) | (data & 0x0f00);
|
|
|
|
LOG("%s:voice %d, control=%04x (raw=%04x & %04x)\n", machine().describe_context(), m_current_page & 0x1f, voice->control, data, mem_mask ^ 0xffff);
|
|
break;
|
|
|
|
case 0x01: /* FC */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->freqcount = (voice->freqcount & ~get_accum_shifted_val(0x00fe, 1)) | (get_accum_shifted_val((data & 0x00fe), 1));
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->freqcount = (voice->freqcount & ~get_accum_shifted_val(0xff00, 1)) | (get_accum_shifted_val((data & 0xff00), 1));
|
|
LOG("%s:voice %d, freq count=%08x\n", machine().describe_context(), m_current_page & 0x1f, get_accum_res(voice->freqcount, 1));
|
|
break;
|
|
|
|
case 0x02: /* STRT (hi) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->start = (voice->start & ~get_accum_shifted_val(0x00ff0000)) | (get_accum_shifted_val((data & 0x00ff) << 16));
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->start = (voice->start & ~get_accum_shifted_val(0x1f000000)) | (get_accum_shifted_val((data & 0x1f00) << 16));
|
|
LOG("%s:voice %d, loop start=%08x\n", machine().describe_context(), m_current_page & 0x1f, get_accum_res(voice->start));
|
|
break;
|
|
|
|
case 0x03: /* STRT (lo) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->start = (voice->start & ~get_accum_shifted_val(0x000000e0)) | (get_accum_shifted_val(data & 0x00e0));
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->start = (voice->start & ~get_accum_shifted_val(0x0000ff00)) | (get_accum_shifted_val(data & 0xff00));
|
|
LOG("%s:voice %d, loop start=%08x\n", machine().describe_context(), m_current_page & 0x1f, get_accum_res(voice->start));
|
|
break;
|
|
|
|
case 0x04: /* END (hi) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->end = (voice->end & ~get_accum_shifted_val(0x00ff0000)) | (get_accum_shifted_val((data & 0x00ff) << 16));
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->end = (voice->end & ~get_accum_shifted_val(0x1f000000)) | (get_accum_shifted_val((data & 0x1f00) << 16));
|
|
#if RAINE_CHECK
|
|
voice->control |= CONTROL_STOP0;
|
|
#endif
|
|
LOG("%s:voice %d, loop end=%08x\n", machine().describe_context(), m_current_page & 0x1f, get_accum_res(voice->end));
|
|
break;
|
|
|
|
case 0x05: /* END (lo) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->end = (voice->end & ~get_accum_shifted_val(0x000000e0)) | (get_accum_shifted_val(data & 0x00e0));
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->end = (voice->end & ~get_accum_shifted_val(0x0000ff00)) | (get_accum_shifted_val(data & 0xff00));
|
|
#if RAINE_CHECK
|
|
voice->control |= CONTROL_STOP0;
|
|
#endif
|
|
LOG("%s:voice %d, loop end=%08x\n", machine().describe_context(), m_current_page & 0x1f, get_accum_res(voice->end));
|
|
break;
|
|
|
|
case 0x06: /* K2 */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->k2 = (voice->k2 & ~0x00f0) | (data & 0x00f0);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->k2 = (voice->k2 & ~0xff00) | (data & 0xff00);
|
|
LOG("%s:voice %d, K2=%03x\n", machine().describe_context(), m_current_page & 0x1f, voice->k2 >> FILTER_SHIFT);
|
|
break;
|
|
|
|
case 0x07: /* K1 */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->k1 = (voice->k1 & ~0x00f0) | (data & 0x00f0);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->k1 = (voice->k1 & ~0xff00) | (data & 0xff00);
|
|
LOG("%s:voice %d, K1=%03x\n", machine().describe_context(), m_current_page & 0x1f, voice->k1 >> FILTER_SHIFT);
|
|
break;
|
|
|
|
case 0x08: /* LVOL */
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->lvol = (voice->lvol & ~0xff00) | (data & 0xff00);
|
|
LOG("%s:voice %d, left vol=%02x\n", machine().describe_context(), m_current_page & 0x1f, voice->lvol >> VOLUME_SHIFT_ES5505);
|
|
break;
|
|
|
|
case 0x09: /* RVOL */
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->rvol = (voice->rvol & ~0xff00) | (data & 0xff00);
|
|
LOG("%s:voice %d, right vol=%02x\n", machine().describe_context(), m_current_page & 0x1f, voice->rvol >> VOLUME_SHIFT_ES5505);
|
|
break;
|
|
|
|
case 0x0a: /* ACC (hi) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->accum = (voice->accum & ~get_accum_shifted_val(0x00ff0000)) | (get_accum_shifted_val((data & 0x00ff) << 16));
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->accum = (voice->accum & ~get_accum_shifted_val(0x1f000000)) | (get_accum_shifted_val((data & 0x1f00) << 16));
|
|
LOG("%s:voice %d, accum=%08x\n", machine().describe_context(), m_current_page & 0x1f, get_accum_res(voice->accum));
|
|
break;
|
|
|
|
case 0x0b: /* ACC (lo) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->accum = (voice->accum & ~get_accum_shifted_val(0x000000ff)) | (get_accum_shifted_val(data & 0x00ff));
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->accum = (voice->accum & ~get_accum_shifted_val(0x0000ff00)) | (get_accum_shifted_val(data & 0xff00));
|
|
LOG("%s:voice %d, accum=%08x\n", machine().describe_context(), m_current_page & 0x1f, get_accum_res(voice->accum));
|
|
break;
|
|
|
|
case 0x0c: /* unused */
|
|
break;
|
|
|
|
case 0x0d: /* ACT */
|
|
if (ACCESSING_BITS_0_7)
|
|
{
|
|
m_active_voices = data & 0x1f;
|
|
m_sample_rate = m_master_clock / (16 * (m_active_voices + 1));
|
|
m_stream->set_sample_rate(m_sample_rate);
|
|
if (!m_sample_rate_changed_cb.isnull())
|
|
m_sample_rate_changed_cb(m_sample_rate);
|
|
|
|
LOG("active voices=%d, sample_rate=%d\n", m_active_voices, m_sample_rate);
|
|
}
|
|
break;
|
|
|
|
case 0x0e: /* IRQV - read only */
|
|
break;
|
|
|
|
case 0x0f: /* PAGE */
|
|
if (ACCESSING_BITS_0_7)
|
|
m_current_page = data & 0x7f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
inline void es5505_device::reg_write_high(es550x_voice *voice, offs_t offset, u16 data, u16 mem_mask)
|
|
{
|
|
switch (offset)
|
|
{
|
|
case 0x00: /* CR */
|
|
voice->control |= 0xf000; // bit 15-12 always 1
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->control = (voice->control & ~0x00ff) | (data & 0x00ff);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->control = (voice->control & ~0x0f00) | (data & 0x0f00);
|
|
|
|
LOG("%s:voice %d, control=%04x (raw=%04x & %04x)\n", machine().describe_context(), m_current_page & 0x1f, voice->control, data, mem_mask);
|
|
break;
|
|
|
|
case 0x01: /* O4(n-1) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->o4n1 = (voice->o4n1 & ~0x00ff) | (data & 0x00ff);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->o4n1 = (s16)((voice->o4n1 & ~0xff00) | (data & 0xff00));
|
|
LOG("%s:voice %d, O4(n-1)=%04x\n", machine().describe_context(), m_current_page & 0x1f, voice->o4n1 & 0xffff);
|
|
break;
|
|
|
|
case 0x02: /* O3(n-1) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->o3n1 = (voice->o3n1 & ~0x00ff) | (data & 0x00ff);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->o3n1 = (s16)((voice->o3n1 & ~0xff00) | (data & 0xff00));
|
|
LOG("%s:voice %d, O3(n-1)=%04x\n", machine().describe_context(), m_current_page & 0x1f, voice->o3n1 & 0xffff);
|
|
break;
|
|
|
|
case 0x03: /* O3(n-2) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->o3n2 = (voice->o3n2 & ~0x00ff) | (data & 0x00ff);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->o3n2 = (s16)((voice->o3n2 & ~0xff00) | (data & 0xff00));
|
|
LOG("%s:voice %d, O3(n-2)=%04x\n", machine().describe_context(), m_current_page & 0x1f, voice->o3n2 & 0xffff);
|
|
break;
|
|
|
|
case 0x04: /* O2(n-1) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->o2n1 = (voice->o2n1 & ~0x00ff) | (data & 0x00ff);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->o2n1 = (s16)((voice->o2n1 & ~0xff00) | (data & 0xff00));
|
|
LOG("%s:voice %d, O2(n-1)=%04x\n", machine().describe_context(), m_current_page & 0x1f, voice->o2n1 & 0xffff);
|
|
break;
|
|
|
|
case 0x05: /* O2(n-2) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->o2n2 = (voice->o2n2 & ~0x00ff) | (data & 0x00ff);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->o2n2 = (s16)((voice->o2n2 & ~0xff00) | (data & 0xff00));
|
|
LOG("%s:voice %d, O2(n-2)=%04x\n", machine().describe_context(), m_current_page & 0x1f, voice->o2n2 & 0xffff);
|
|
break;
|
|
|
|
case 0x06: /* O1(n-1) */
|
|
if (ACCESSING_BITS_0_7)
|
|
voice->o1n1 = (voice->o1n1 & ~0x00ff) | (data & 0x00ff);
|
|
if (ACCESSING_BITS_8_15)
|
|
voice->o1n1 = (s16)((voice->o1n1 & ~0xff00) | (data & 0xff00));
|
|
LOG("%s:voice %d, O1(n-1)=%04x (accum=%08x)\n", machine().describe_context(), m_current_page & 0x1f, voice->o1n1 & 0xffff, get_accum_res(voice->accum));
|
|
break;
|
|
|
|
case 0x07:
|
|
case 0x08:
|
|
case 0x09:
|
|
case 0x0a:
|
|
case 0x0b:
|
|
case 0x0c: /* unused */
|
|
break;
|
|
|
|
case 0x0d: /* ACT */
|
|
if (ACCESSING_BITS_0_7)
|
|
{
|
|
m_active_voices = data & 0x1f;
|
|
m_sample_rate = m_master_clock / (16 * (m_active_voices + 1));
|
|
m_stream->set_sample_rate(m_sample_rate);
|
|
if (!m_sample_rate_changed_cb.isnull())
|
|
m_sample_rate_changed_cb(m_sample_rate);
|
|
|
|
LOG("active voices=%d, sample_rate=%d\n", m_active_voices, m_sample_rate);
|
|
}
|
|
break;
|
|
|
|
case 0x0e: /* IRQV - read only */
|
|
break;
|
|
|
|
case 0x0f: /* PAGE */
|
|
if (ACCESSING_BITS_0_7)
|
|
m_current_page = data & 0x7f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
inline void es5505_device::reg_write_test(es550x_voice *voice, offs_t offset, u16 data, u16 mem_mask)
|
|
{
|
|
switch (offset)
|
|
{
|
|
case 0x00: /* CH0L */
|
|
case 0x01: /* CH0R */
|
|
case 0x02: /* CH1L */
|
|
case 0x03: /* CH1R */
|
|
case 0x04: /* CH2L */
|
|
case 0x05: /* CH2R */
|
|
case 0x06: /* CH3L */
|
|
case 0x07: /* CH3R */
|
|
break;
|
|
|
|
case 0x08: /* SERMODE */
|
|
m_mode |= 0x7f8; // bit 10-3 always 1
|
|
if (ACCESSING_BITS_8_15)
|
|
m_mode = (m_mode & ~0xf800) | (data & 0xf800); // MSB[4:0] (unknown purpose)
|
|
if (ACCESSING_BITS_0_7)
|
|
m_mode = (m_mode & ~0x0007) | (data & 0x0007); // SONY/BB, TEST, A/D
|
|
break;
|
|
|
|
case 0x09: /* PAR */
|
|
break;
|
|
|
|
case 0x0d: /* ACT */
|
|
if (ACCESSING_BITS_0_7)
|
|
{
|
|
m_active_voices = data & 0x1f;
|
|
m_sample_rate = m_master_clock / (16 * (m_active_voices + 1));
|
|
m_stream->set_sample_rate(m_sample_rate);
|
|
if (!m_sample_rate_changed_cb.isnull())
|
|
m_sample_rate_changed_cb(m_sample_rate);
|
|
|
|
LOG("active voices=%d, sample_rate=%d\n", m_active_voices, m_sample_rate);
|
|
}
|
|
break;
|
|
|
|
case 0x0e: /* IRQV - read only */
|
|
break;
|
|
|
|
case 0x0f: /* PAGE */
|
|
if (ACCESSING_BITS_0_7)
|
|
m_current_page = data & 0x7f;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
WRITE16_MEMBER(es5505_device::write)
|
|
{
|
|
es550x_voice *voice = &m_voice[m_current_page & 0x1f];
|
|
|
|
// logerror("%s:ES5505 write %02x/%02x = %04x & %04x\n", machine().describe_context(), m_current_page, offset, data, mem_mask);
|
|
|
|
/* force an update */
|
|
m_stream->update();
|
|
|
|
/* switch off the page and register */
|
|
if (m_current_page < 0x20)
|
|
reg_write_low(voice, offset, data, mem_mask);
|
|
else if (m_current_page < 0x40)
|
|
reg_write_high(voice, offset, data, mem_mask);
|
|
else
|
|
reg_write_test(voice, offset, data, mem_mask);
|
|
}
|
|
|
|
|
|
|
|
/**********************************************************************************************
|
|
|
|
reg_read -- read from the specified ES5505 register
|
|
|
|
***********************************************************************************************/
|
|
|
|
inline u16 es5505_device::reg_read_low(es550x_voice *voice, offs_t offset)
|
|
{
|
|
u16 result = 0;
|
|
|
|
switch (offset)
|
|
{
|
|
case 0x00: /* CR */
|
|
result = voice->control | 0xf000;
|
|
break;
|
|
|
|
case 0x01: /* FC */
|
|
result = get_accum_res(voice->freqcount, 1);
|
|
break;
|
|
|
|
case 0x02: /* STRT (hi) */
|
|
result = get_accum_res(voice->start) >> 16;
|
|
break;
|
|
|
|
case 0x03: /* STRT (lo) */
|
|
result = get_accum_res(voice->start);
|
|
break;
|
|
|
|
case 0x04: /* END (hi) */
|
|
result = get_accum_res(voice->end) >> 16;
|
|
break;
|
|
|
|
case 0x05: /* END (lo) */
|
|
result = get_accum_res(voice->end);
|
|
break;
|
|
|
|
case 0x06: /* K2 */
|
|
result = voice->k2;
|
|
break;
|
|
|
|
case 0x07: /* K1 */
|
|
result = voice->k1;
|
|
break;
|
|
|
|
case 0x08: /* LVOL */
|
|
result = voice->lvol;
|
|
break;
|
|
|
|
case 0x09: /* RVOL */
|
|
result = voice->rvol;
|
|
break;
|
|
|
|
case 0x0a: /* ACC (hi) */
|
|
result = get_accum_res(voice->accum) >> 16;
|
|
break;
|
|
|
|
case 0x0b: /* ACC (lo) */
|
|
result = get_accum_res(voice->accum);
|
|
break;
|
|
|
|
case 0x0c: /* unused */
|
|
break;
|
|
|
|
case 0x0d: /* ACT */
|
|
result = m_active_voices;
|
|
break;
|
|
|
|
case 0x0e: /* IRQV */
|
|
result = m_irqv;
|
|
if (!machine().side_effects_disabled())
|
|
update_internal_irq_state();
|
|
break;
|
|
|
|
case 0x0f: /* PAGE */
|
|
result = m_current_page;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
inline u16 es5505_device::reg_read_high(es550x_voice *voice, offs_t offset)
|
|
{
|
|
u16 result = 0;
|
|
|
|
switch (offset)
|
|
{
|
|
case 0x00: /* CR */
|
|
result = voice->control | 0xf000;
|
|
break;
|
|
|
|
case 0x01: /* O4(n-1) */
|
|
result = voice->o4n1 & 0xffff;
|
|
break;
|
|
|
|
case 0x02: /* O3(n-1) */
|
|
result = voice->o3n1 & 0xffff;
|
|
break;
|
|
|
|
case 0x03: /* O3(n-2) */
|
|
result = voice->o3n2 & 0xffff;
|
|
break;
|
|
|
|
case 0x04: /* O2(n-1) */
|
|
result = voice->o2n1 & 0xffff;
|
|
break;
|
|
|
|
case 0x05: /* O2(n-2) */
|
|
result = voice->o2n2 & 0xffff;
|
|
break;
|
|
|
|
case 0x06: /* O1(n-1) */
|
|
/* special case for the Taito F3 games: they set the accumulator on a stopped */
|
|
/* voice and assume the filters continue to process the data. They then read */
|
|
/* the O1(n-1) in order to extract raw data from the sound ROMs. Since we don't */
|
|
/* want to waste time filtering stopped channels, we just look for a read from */
|
|
/* this register on a stopped voice, and return the raw sample data at the */
|
|
/* accumulator */
|
|
if ((voice->control & CONTROL_STOPMASK) && m_region_base[get_bank(voice->control)])
|
|
{
|
|
voice->o1n1 = m_region_base[get_bank(voice->control)][voice->exbank + get_integer_addr(voice->accum)];
|
|
// logerror("%02x %08x ==> %08x\n",voice->o1n1,get_bank(voice->control),voice->exbank + get_integer_addr(voice->accum));
|
|
}
|
|
result = voice->o1n1 & 0xffff;
|
|
break;
|
|
|
|
case 0x07:
|
|
case 0x08:
|
|
case 0x09:
|
|
case 0x0a:
|
|
case 0x0b:
|
|
case 0x0c: /* unused */
|
|
break;
|
|
|
|
case 0x0d: /* ACT */
|
|
result = m_active_voices;
|
|
break;
|
|
|
|
case 0x0e: /* IRQV */
|
|
result = m_irqv;
|
|
if (!machine().side_effects_disabled())
|
|
update_internal_irq_state();
|
|
break;
|
|
|
|
case 0x0f: /* PAGE */
|
|
result = m_current_page;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
inline u16 es5505_device::reg_read_test(es550x_voice *voice, offs_t offset)
|
|
{
|
|
u16 result = 0;
|
|
|
|
switch (offset)
|
|
{
|
|
case 0x00: /* CH0L */
|
|
case 0x01: /* CH0R */
|
|
case 0x02: /* CH1L */
|
|
case 0x03: /* CH1R */
|
|
case 0x04: /* CH2L */
|
|
case 0x05: /* CH2R */
|
|
case 0x06: /* CH3L */
|
|
case 0x07: /* CH3R */
|
|
break;
|
|
|
|
case 0x08: /* SERMODE */
|
|
result = m_mode | 0x7f8;
|
|
break;
|
|
|
|
case 0x09: /* PAR */
|
|
if (!m_read_port_cb.isnull())
|
|
result = m_read_port_cb(0) & 0xffc0; // 10 bit, 15:6
|
|
break;
|
|
|
|
/* The following are global, and thus accessible form all pages */
|
|
case 0x0d: /* ACT */
|
|
result = m_active_voices;
|
|
break;
|
|
|
|
case 0x0e: /* IRQV */
|
|
result = m_irqv;
|
|
if (!machine().side_effects_disabled())
|
|
update_internal_irq_state();
|
|
break;
|
|
|
|
case 0x0f: /* PAGE */
|
|
result = m_current_page;
|
|
break;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
|
|
READ16_MEMBER(es5505_device::read)
|
|
{
|
|
es550x_voice *voice = &m_voice[m_current_page & 0x1f];
|
|
u16 result;
|
|
|
|
LOG("read from %02x/%02x -> ", m_current_page, offset);
|
|
|
|
/* force an update */
|
|
m_stream->update();
|
|
|
|
/* switch off the page and register */
|
|
if (m_current_page < 0x20)
|
|
result = reg_read_low(voice, offset);
|
|
else if (m_current_page < 0x40)
|
|
result = reg_read_high(voice, offset);
|
|
else
|
|
result = reg_read_test(voice, offset);
|
|
|
|
LOG("%04x (accum=%08x)\n", result, voice->accum);
|
|
|
|
/* return the high byte */
|
|
return result;
|
|
}
|
|
|
|
|
|
|
|
void es5505_device::voice_bank_w(int voice, int bank)
|
|
{
|
|
#if RAINE_CHECK
|
|
m_voice[voice].control = CONTROL_STOPMASK;
|
|
#endif
|
|
m_voice[voice].exbank=bank;
|
|
}
|
|
|
|
|
|
//-------------------------------------------------
|
|
// sound_stream_update - handle a stream update
|
|
//-------------------------------------------------
|
|
|
|
void es550x_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
|
|
{
|
|
#if ES5506_MAKE_WAVS
|
|
/* start the logging once we have a sample rate */
|
|
if (m_sample_rate)
|
|
{
|
|
if (!m_wavraw)
|
|
m_wavraw = wav_open("raw.wav", m_sample_rate, 2);
|
|
}
|
|
#endif
|
|
|
|
/* loop until all samples are output */
|
|
int offset = 0;
|
|
while (samples)
|
|
{
|
|
int length = (samples > MAX_SAMPLE_CHUNK) ? MAX_SAMPLE_CHUNK : samples;
|
|
|
|
generate_samples(outputs, offset, length);
|
|
|
|
#if ES5506_MAKE_WAVS
|
|
/* log the raw data */
|
|
if (m_wavraw)
|
|
{
|
|
/* determine left/right source data */
|
|
s32 *lsrc = m_scratch, *rsrc = m_scratch + length;
|
|
int channel;
|
|
memset(lsrc, 0, sizeof(s32) * length * 2);
|
|
/* loop over the output channels */
|
|
for (channel = 0; channel < m_channels; channel++)
|
|
{
|
|
s32 *l = outputs[(channel << 1)] + offset;
|
|
s32 *r = outputs[(channel << 1) + 1] + offset;
|
|
/* add the current channel's samples to the WAV data */
|
|
for (samp = 0; samp < length; samp++)
|
|
{
|
|
lsrc[samp] += l[samp];
|
|
rsrc[samp] += r[samp];
|
|
}
|
|
}
|
|
wav_add_data_32lr(m_wavraw, lsrc, rsrc, length, 4);
|
|
}
|
|
#endif
|
|
|
|
/* account for these samples */
|
|
offset += length;
|
|
samples -= length;
|
|
}
|
|
}
|