543 lines
12 KiB
C++
543 lines
12 KiB
C++
// license:BSD-3-Clause
|
|
// copyright-holders:Ville Linde
|
|
/*
|
|
Ricoh RF5C400 emulator
|
|
|
|
Written by Ville Linde
|
|
Improvements by the hoot development team
|
|
|
|
history -
|
|
2007-02-08 hoot development team
|
|
looping
|
|
stereo panning
|
|
8-bit sample support
|
|
|
|
2007-02-16 hoot development team
|
|
envelope
|
|
fixed volume table
|
|
*/
|
|
|
|
#include "emu.h"
|
|
#include "rf5c400.h"
|
|
|
|
namespace {
|
|
|
|
int volume_table[256];
|
|
double pan_table[0x64];
|
|
|
|
void init_static_tables()
|
|
{
|
|
// init volume/pan tables
|
|
double max = 255.0;
|
|
for (int i = 0; i < 256; i++) {
|
|
volume_table[i] = uint16_t(max);
|
|
max /= pow(10.0, double((4.5 / (256.0 / 16.0)) / 20));
|
|
}
|
|
for (int i = 0; i < 0x48; i++) {
|
|
pan_table[i] = sqrt(double(0x47 - i)) / sqrt(double(0x47));
|
|
}
|
|
for (int i = 0x48; i < 0x64; i++) {
|
|
pan_table[i] = 0.0;
|
|
}
|
|
}
|
|
|
|
|
|
/* PCM type */
|
|
enum
|
|
{
|
|
TYPE_MASK = 0x00C0,
|
|
TYPE_16 = 0x0000,
|
|
TYPE_8LOW = 0x0040,
|
|
TYPE_8HIGH = 0x0080
|
|
};
|
|
|
|
/* envelope phase */
|
|
enum
|
|
{
|
|
PHASE_NONE = 0,
|
|
PHASE_ATTACK,
|
|
PHASE_DECAY,
|
|
PHASE_RELEASE
|
|
};
|
|
|
|
} // anonymous namespace
|
|
|
|
|
|
// device type definition
|
|
DEFINE_DEVICE_TYPE(RF5C400, rf5c400_device, "rf5c400", "Ricoh RF5C400")
|
|
|
|
|
|
rf5c400_device::envelope_tables::envelope_tables()
|
|
{
|
|
std::fill(std::begin(m_ar), std::end(m_ar), 0.0);
|
|
std::fill(std::begin(m_dr), std::end(m_dr), 0.0);
|
|
std::fill(std::begin(m_rr), std::end(m_rr), 0.0);
|
|
}
|
|
|
|
void rf5c400_device::envelope_tables::init(uint32_t clock)
|
|
{
|
|
/* envelope parameter (experimental) */
|
|
static constexpr double ENV_AR_SPEED = 0.1;
|
|
static constexpr int ENV_MIN_AR = 0x02;
|
|
static constexpr int ENV_MAX_AR = 0x80;
|
|
static constexpr double ENV_DR_SPEED = 2.0;
|
|
static constexpr int ENV_MIN_DR = 0x20;
|
|
static constexpr int ENV_MAX_DR = 0x73;
|
|
static constexpr double ENV_RR_SPEED = 0.7;
|
|
static constexpr int ENV_MIN_RR = 0x20;
|
|
static constexpr int ENV_MAX_RR = 0x54;
|
|
|
|
double r;
|
|
|
|
// attack
|
|
r = 1.0 / (ENV_AR_SPEED * (clock / 384));
|
|
for (int i = 0; i < ENV_MIN_AR; i++)
|
|
m_ar[i] = 1.0;
|
|
for (int i = ENV_MIN_AR; i < ENV_MAX_AR; i++)
|
|
m_ar[i] = r * (ENV_MAX_AR - i) / (ENV_MAX_AR - ENV_MIN_AR);
|
|
for (int i = ENV_MAX_AR; i < 0x9f; i++)
|
|
m_ar[i] = 0.0;
|
|
|
|
// decay
|
|
r = -5.0 / (ENV_DR_SPEED * (clock / 384));
|
|
for (int i = 0; i < ENV_MIN_DR; i++)
|
|
m_dr[i] = r;
|
|
for (int i = ENV_MIN_DR; i < ENV_MAX_DR; i++)
|
|
m_dr[i] = r * (ENV_MAX_DR - i) / (ENV_MAX_DR - ENV_MIN_DR);
|
|
for (int i = ENV_MAX_DR; i < 0x9f; i++)
|
|
m_dr[i] = 0.0;
|
|
|
|
// release
|
|
r = -5.0 / (ENV_RR_SPEED * (clock / 384));
|
|
for (int i = 0; i < ENV_MIN_RR; i++)
|
|
m_rr[i] = r;
|
|
for (int i = ENV_MIN_RR; i < ENV_MAX_RR; i++)
|
|
m_rr[i] = r * (ENV_MAX_RR - i) / (ENV_MAX_RR - ENV_MIN_RR);
|
|
for (int i = ENV_MAX_RR; i < 0x9f; i++)
|
|
m_rr[i] = 0.0;
|
|
}
|
|
|
|
|
|
//**************************************************************************
|
|
// LIVE DEVICE
|
|
//**************************************************************************
|
|
|
|
//-------------------------------------------------
|
|
// rf5c400_device - constructor
|
|
//-------------------------------------------------
|
|
|
|
rf5c400_device::rf5c400_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
|
|
: device_t(mconfig, RF5C400, tag, owner, clock)
|
|
, device_sound_interface(mconfig, *this)
|
|
, m_rom(*this, DEVICE_SELF)
|
|
, m_stream(nullptr)
|
|
, m_env_tables()
|
|
{
|
|
}
|
|
|
|
|
|
|
|
//-------------------------------------------------
|
|
// device_start - device-specific startup
|
|
//-------------------------------------------------
|
|
|
|
void rf5c400_device::device_start()
|
|
{
|
|
init_static_tables();
|
|
m_env_tables.init(clock());
|
|
|
|
// init channel info
|
|
for (rf5c400_channel &chan : m_channels)
|
|
{
|
|
chan.env_phase = PHASE_NONE;
|
|
chan.env_level = 0.0;
|
|
chan.env_step = 0.0;
|
|
chan.env_scale = 1.0;
|
|
}
|
|
|
|
for (int i = 0; i < ARRAY_LENGTH(m_channels); i++)
|
|
{
|
|
save_item(NAME(m_channels[i].startH), i);
|
|
save_item(NAME(m_channels[i].startL), i);
|
|
save_item(NAME(m_channels[i].freq), i);
|
|
save_item(NAME(m_channels[i].endL), i);
|
|
save_item(NAME(m_channels[i].endHloopH), i);
|
|
save_item(NAME(m_channels[i].loopL), i);
|
|
save_item(NAME(m_channels[i].pan), i);
|
|
save_item(NAME(m_channels[i].effect), i);
|
|
save_item(NAME(m_channels[i].volume), i);
|
|
save_item(NAME(m_channels[i].attack), i);
|
|
save_item(NAME(m_channels[i].decay), i);
|
|
save_item(NAME(m_channels[i].release), i);
|
|
save_item(NAME(m_channels[i].cutoff), i);
|
|
save_item(NAME(m_channels[i].pos), i);
|
|
save_item(NAME(m_channels[i].step), i);
|
|
save_item(NAME(m_channels[i].keyon), i);
|
|
save_item(NAME(m_channels[i].env_phase), i);
|
|
save_item(NAME(m_channels[i].env_level), i);
|
|
save_item(NAME(m_channels[i].env_step), i);
|
|
save_item(NAME(m_channels[i].env_scale), i);
|
|
}
|
|
|
|
m_stream = stream_alloc(0, 2, clock() / 384);
|
|
|
|
m_rommask = (m_rom.length()/2) - 1;
|
|
}
|
|
|
|
//-------------------------------------------------
|
|
// sound_stream_update - handle a stream update
|
|
//-------------------------------------------------
|
|
|
|
void rf5c400_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
|
|
{
|
|
int i, ch;
|
|
int16_t *rom = m_rom;
|
|
uint32_t end, loop;
|
|
uint64_t pos;
|
|
uint8_t vol, lvol, rvol, type;
|
|
uint8_t env_phase;
|
|
double env_level, env_step, env_rstep;
|
|
|
|
memset(outputs[0], 0, samples * sizeof(*outputs[0]));
|
|
memset(outputs[1], 0, samples * sizeof(*outputs[1]));
|
|
|
|
for (ch=0; ch < 32; ch++)
|
|
{
|
|
rf5c400_channel *channel = &m_channels[ch];
|
|
stream_sample_t *buf0 = outputs[0];
|
|
stream_sample_t *buf1 = outputs[1];
|
|
|
|
// start = ((channel->startH & 0xFF00) << 8) | channel->startL;
|
|
end = ((channel->endHloopH & 0xFF) << 16) | channel->endL;
|
|
loop = ((channel->endHloopH & 0xFF00) << 8) | channel->loopL;
|
|
pos = channel->pos;
|
|
vol = channel->volume & 0xFF;
|
|
lvol = channel->pan & 0xFF;
|
|
rvol = channel->pan >> 8;
|
|
type = (channel->volume >> 8) & TYPE_MASK;
|
|
|
|
env_phase = channel->env_phase;
|
|
env_level = channel->env_level;
|
|
env_step = channel->env_step;
|
|
env_rstep = env_step * channel->env_scale;
|
|
|
|
for (i=0; i < samples; i++)
|
|
{
|
|
int16_t tmp;
|
|
int32_t sample;
|
|
|
|
if (env_phase == PHASE_NONE) break;
|
|
|
|
tmp = rom[(pos>>16) & m_rommask];
|
|
switch ( type )
|
|
{
|
|
case TYPE_16:
|
|
sample = tmp;
|
|
break;
|
|
case TYPE_8LOW:
|
|
sample = (int16_t)(tmp << 8);
|
|
break;
|
|
case TYPE_8HIGH:
|
|
sample = (int16_t)(tmp & 0xFF00);
|
|
break;
|
|
default:
|
|
sample = 0;
|
|
break;
|
|
}
|
|
|
|
if ( sample & 0x8000 )
|
|
{
|
|
sample ^= 0x7FFF;
|
|
}
|
|
|
|
env_level += env_rstep;
|
|
switch (env_phase)
|
|
{
|
|
case PHASE_ATTACK:
|
|
if (env_level >= 1.0)
|
|
{
|
|
env_phase = PHASE_DECAY;
|
|
env_level = 1.0;
|
|
if ((channel->decay & 0x0080) || (channel->decay == 0x100))
|
|
{
|
|
env_step = 0.0;
|
|
}
|
|
else
|
|
{
|
|
env_step = m_env_tables.dr(*channel);
|
|
}
|
|
env_rstep = env_step * channel->env_scale;
|
|
}
|
|
break;
|
|
case PHASE_DECAY:
|
|
if (env_level <= 0.0)
|
|
{
|
|
env_phase = PHASE_NONE;
|
|
env_level = 0.0;
|
|
env_step = 0.0;
|
|
env_rstep = 0.0;
|
|
}
|
|
break;
|
|
case PHASE_RELEASE:
|
|
if (env_level <= 0.0)
|
|
{
|
|
env_phase = PHASE_NONE;
|
|
env_level = 0.0;
|
|
env_step = 0.0;
|
|
env_rstep = 0.0;
|
|
}
|
|
break;
|
|
}
|
|
|
|
sample *= volume_table[vol];
|
|
sample = (sample >> 9) * env_level;
|
|
*buf0++ += sample * pan_table[lvol];
|
|
*buf1++ += sample * pan_table[rvol];
|
|
|
|
pos += channel->step;
|
|
if ( (pos>>16) > m_rom.length() || (pos>>16) > end)
|
|
{
|
|
pos -= loop<<16;
|
|
pos &= 0xFFFFFF0000U;
|
|
}
|
|
|
|
}
|
|
channel->pos = pos;
|
|
|
|
channel->env_phase = env_phase;
|
|
channel->env_level = env_level;
|
|
channel->env_step = env_step;
|
|
}
|
|
}
|
|
|
|
|
|
/*****************************************************************************/
|
|
|
|
static uint16_t rf5c400_status = 0; // a static one of these for all instances of the chip? how does that work?
|
|
READ16_MEMBER( rf5c400_device::rf5c400_r )
|
|
{
|
|
switch(offset)
|
|
{
|
|
case 0x00:
|
|
{
|
|
return rf5c400_status;
|
|
}
|
|
|
|
case 0x04:
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
case 0x13: // memory read
|
|
{
|
|
return m_rom[m_ext_mem_address];
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
WRITE16_MEMBER( rf5c400_device::rf5c400_w )
|
|
{
|
|
if (offset < 0x400)
|
|
{
|
|
switch(offset)
|
|
{
|
|
case 0x00:
|
|
{
|
|
rf5c400_status = data;
|
|
break;
|
|
}
|
|
|
|
case 0x01: // channel control
|
|
{
|
|
int ch = data & 0x1f;
|
|
switch ( data & 0x60 )
|
|
{
|
|
case 0x60:
|
|
m_channels[ch].pos =
|
|
((m_channels[ch].startH & 0xFF00) << 8) | m_channels[ch].startL;
|
|
m_channels[ch].pos <<= 16;
|
|
|
|
m_channels[ch].env_phase = PHASE_ATTACK;
|
|
m_channels[ch].env_level = 0.0;
|
|
m_channels[ch].env_step = m_env_tables.ar(m_channels[ch]);
|
|
break;
|
|
case 0x40:
|
|
if (m_channels[ch].env_phase != PHASE_NONE)
|
|
{
|
|
m_channels[ch].env_phase = PHASE_RELEASE;
|
|
if (m_channels[ch].release & 0x0080)
|
|
{
|
|
m_channels[ch].env_step = 0.0;
|
|
}
|
|
else
|
|
{
|
|
m_channels[ch].env_step = m_env_tables.rr(m_channels[ch]);
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
m_channels[ch].env_phase = PHASE_NONE;
|
|
m_channels[ch].env_level = 0.0;
|
|
m_channels[ch].env_step = 0.0;
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 0x08: // relative to env attack (channel no)
|
|
case 0x09: // relative to env attack (0x0c00/ 0x1c00)
|
|
|
|
case 0x11: // memory r/w address, bits 15 - 0
|
|
{
|
|
m_ext_mem_address &= ~0xffff;
|
|
m_ext_mem_address |= data;
|
|
break;
|
|
}
|
|
case 0x12: // memory r/w address, bits 23 - 16
|
|
{
|
|
m_ext_mem_address &= 0xffff;
|
|
m_ext_mem_address |= (uint32_t)(data) << 16;
|
|
break;
|
|
}
|
|
case 0x13: // memory write data
|
|
{
|
|
m_ext_mem_data = data;
|
|
break;
|
|
}
|
|
|
|
case 0x14: // memory write
|
|
{
|
|
if ((data & 0x3) == 3)
|
|
{
|
|
m_rom[m_ext_mem_address] = m_ext_mem_data;
|
|
}
|
|
break;
|
|
}
|
|
|
|
case 0x21: // reverb(character).w
|
|
case 0x32: // reverb(pre-lpf).w
|
|
case 0x2B: // reverb(level).w
|
|
case 0x20: // ???.b : reverb(time).b
|
|
|
|
case 0x2C: // chorus(level).w
|
|
case 0x30: // chorus(rate).w
|
|
case 0x22: // chorus(macro).w
|
|
case 0x23: // chorus(depth).w
|
|
case 0x24: // chorus(macro).w
|
|
case 0x2F: // chorus(depth).w
|
|
case 0x27: // chorus(send level to reverb).w
|
|
|
|
default:
|
|
{
|
|
//osd_printf_debug("%s:rf5c400_w: %08X, %08X, %08X\n", machine().describe_context(), data, offset, mem_mask);
|
|
break;
|
|
}
|
|
}
|
|
//osd_printf_debug("%s:rf5c400_w: %08X, %08X, %08X at %08X\n", machine().describe_context(), data, offset, mem_mask);
|
|
}
|
|
else
|
|
{
|
|
// channel registers
|
|
int ch = (offset >> 5) & 0x1f;
|
|
int reg = (offset & 0x1f);
|
|
|
|
rf5c400_channel *channel = &m_channels[ch];
|
|
|
|
switch (reg)
|
|
{
|
|
case 0x00: // sample start address, bits 23 - 16
|
|
{
|
|
channel->startH = data;
|
|
break;
|
|
}
|
|
case 0x01: // sample start address, bits 15 - 0
|
|
{
|
|
channel->startL = data;
|
|
break;
|
|
}
|
|
case 0x02: // sample playing frequency
|
|
{
|
|
channel->step = ((data & 0x1fff) << (data >> 13)) * 4;
|
|
channel->freq = data;
|
|
break;
|
|
}
|
|
case 0x03: // sample end address, bits 15 - 0
|
|
{
|
|
channel->endL = data;
|
|
break;
|
|
}
|
|
case 0x04: // sample end address, bits 23 - 16 , sample loop 23 - 16
|
|
{
|
|
channel->endHloopH = data;
|
|
break;
|
|
}
|
|
case 0x05: // sample loop offset, bits 15 - 0
|
|
{
|
|
channel->loopL = data;
|
|
break;
|
|
}
|
|
case 0x06: // channel volume
|
|
{
|
|
channel->pan = data;
|
|
break;
|
|
}
|
|
case 0x07: // effect depth
|
|
{
|
|
// 0xCCRR: CC = chorus send depth, RR = reverb send depth
|
|
channel->effect = data;
|
|
break;
|
|
}
|
|
case 0x08: // volume, flag
|
|
{
|
|
channel->volume = data;
|
|
break;
|
|
}
|
|
case 0x09: // env attack
|
|
{
|
|
// 0x0100: max speed (in case of attack <= 0x40)
|
|
// 0xXX40: XX = attack-0x3f (encoded) (in case of attack > 0x40)
|
|
//
|
|
channel->attack = data;
|
|
break;
|
|
}
|
|
case 0x0A: // relative to env attack ?
|
|
{
|
|
// always 0x0100
|
|
break;
|
|
}
|
|
case 0x0B: // relative to env decay ?
|
|
{
|
|
// always 0x0100
|
|
break;
|
|
}
|
|
case 0x0C: // env decay
|
|
{
|
|
// 0xXX70: XX = decay (encoded) (in case of decay > 0x71)
|
|
// 0xXX80: XX = decay (encoded) (in case of decay <= 0x71)
|
|
channel->decay = data;
|
|
break;
|
|
}
|
|
case 0x0D: // relative to env release ?
|
|
{
|
|
// always 0x0100
|
|
break;
|
|
}
|
|
case 0x0E: // env release
|
|
{
|
|
// 0xXX70: XX = release-0x1f (encoded) (0x01 if release <= 0x20)
|
|
channel->release = data;
|
|
break;
|
|
}
|
|
case 0x10: // resonance, cutoff freq.
|
|
{
|
|
// bit 15-12: resonance
|
|
// bit 11-0 : cutoff frequency
|
|
channel->cutoff = data;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|