ymz774: add volume delayed transition, CHAN/SEQ pause/resume,

ymz770: handle CHAN/SEQ "keep playing" command,
document other known SPU in this series
This commit is contained in:
MetalliC 2017-12-22 18:01:24 +02:00
parent 07b39af9fe
commit 6d7f211e56
3 changed files with 90 additions and 31 deletions

View File

@ -2,7 +2,7 @@
// copyright-holders:Olivier Galibert, R. Belmont, MetalliC
/***************************************************************************
Yamaha YMZ770C and YMZ774
Yamaha YMZ770C "AMMS-A" and YMZ774 "AMMS2C"
Emulation by R. Belmont and MetalliC
AMM decode by Olivier Galibert
@ -10,17 +10,22 @@
-----
TODO:
- What does channel ATBL mean?
- SAC (Simple Access Codes) what it for and how used ?
- how triggered SAC (Simple Access Codes) playback ?
770:
- verify if pan 100% correct
- sequencer timers and triggers not implemented (seems used in Deathsmiles ending tune)
774:
- 4 channel output
- Equalizer
- volume/pan delayed transition (used few times during orleg2 attract mode)
- channel pause/resume (seems not used in games)
- sequencer pause/resume (seems not used in games)
- sequencer off trigger (seems not used in games)
- pan delayed transition (not used in games)
- sequencer off trigger (not used in games)
known SPUs in this series:
YMZ770B ???? Capcom medal hardware (alien.cpp), sample format is not AMM, in other parts looks like 770C
YMZ770C AMMS-A Cave CV1000
YMZ773 AMMS2
YMZ775 AMMS2B
YMZ774 AMMS2C IGS PGM2
YMZ776 AMMS3 uses AM3 sample format (.mp3 derivative ?)
***************************************************************************/
@ -30,7 +35,7 @@ TODO:
// device type definition
DEFINE_DEVICE_TYPE(YMZ770, ymz770_device, "ymz770", "Yamaha YMZ770C AMMS-A")
DEFINE_DEVICE_TYPE(YMZ774, ymz774_device, "ymz774", "Yamaha YMZ774 AMMS2")
DEFINE_DEVICE_TYPE(YMZ774, ymz774_device, "ymz774", "Yamaha YMZ774 AMMS2C")
//-------------------------------------------------
// ymz770_device - constructor
@ -94,11 +99,13 @@ void ymz770_device::device_start()
save_item(NAME(m_channels[ch].pan1), ch);
save_item(NAME(m_channels[ch].pan1_delay), ch);
save_item(NAME(m_channels[ch].volume), ch);
save_item(NAME(m_channels[ch].volume_target), ch);
save_item(NAME(m_channels[ch].volume_delay), ch);
save_item(NAME(m_channels[ch].volume2), ch);
save_item(NAME(m_channels[ch].loop), ch);
save_item(NAME(m_channels[ch].is_playing), ch);
save_item(NAME(m_channels[ch].last_block), ch);
save_item(NAME(m_channels[ch].is_paused), ch);
save_item(NAME(m_channels[ch].output_remaining), ch);
save_item(NAME(m_channels[ch].output_ptr), ch);
save_item(NAME(m_channels[ch].atbl), ch);
@ -114,6 +121,7 @@ void ymz770_device::device_start()
save_item(NAME(m_sequences[ch].loop), ch);
save_item(NAME(m_sequences[ch].bank), ch);
save_item(NAME(m_sequences[ch].is_playing), ch);
save_item(NAME(m_sequences[ch].is_paused), ch);
save_item(NAME(m_sequences[ch].offset), ch);
}
for (int ch = 0; ch < 8; ch++)
@ -141,10 +149,12 @@ void ymz770_device::device_reset()
channel.pan1 = 64;
channel.pan1_delay = 0;
channel.volume = 0;
channel.volume_target = 0;
channel.volume_delay = 0;
channel.volume2 = 0;
channel.loop = 0;
channel.is_playing = false;
channel.is_paused = false;
channel.output_remaining = 0;
channel.decoder->clear();
}
@ -157,6 +167,7 @@ void ymz770_device::device_reset()
sequence.loop = 0;
sequence.bank = 0;
sequence.is_playing = false;
sequence.is_paused = false;
}
for (auto & sqc : m_sqcs)
{
@ -191,7 +202,7 @@ void ymz770_device::sound_stream_update(sound_stream &stream, stream_sample_t **
if (channel.output_remaining > 0)
{
// force finish current block
int32_t smpl = (channel.output_data[channel.output_ptr++] * channel.volume) >> 7; // volume is linear, 0 - 128 (100%)
int32_t smpl = ((int32_t)channel.output_data[channel.output_ptr++] * (channel.volume >> 17)) >> 7; // volume is linear, 0 - 128 (100%)
smpl = (smpl * channel.volume2) >> 7;
mixr += (smpl * channel.pan) >> 7; // pan seems linear, 0 - 128, where 0 = 100% left, 128 = 100% right, 64 = 50% left 50% right
mixl += (smpl * (128 - channel.pan)) >> 7;
@ -201,7 +212,7 @@ void ymz770_device::sound_stream_update(sound_stream &stream, stream_sample_t **
channel.decoder->clear();
}
else if (channel.is_playing)
else if (channel.is_playing && !channel.is_paused)
{
retry:
if (channel.last_block)
@ -239,7 +250,7 @@ retry:
channel.output_remaining--;
channel.output_ptr = 1;
int32_t smpl = (channel.output_data[0] * channel.volume) >> 7;
int32_t smpl = ((int32_t)channel.output_data[0] * (channel.volume >> 17)) >> 7;
smpl = (smpl * channel.volume2) >> 7;
mixr += (smpl * channel.pan) >> 7;
mixl += (smpl * (128 - channel.pan)) >> 7;
@ -251,7 +262,7 @@ retry:
mixl *= m_vlma;
mixr >>= 7 - m_bsl;
mixl >>= 7 - m_bsl;
// Clip limiter: 0 - off, 1 - 6.02 dB (100%), 2 - 4.86 dB (87.5%), 3 - 3.52 dB (75%). values taken from YMZ773 docs, might be incorrect for YMZ770.
// Clip limiter: 0 - off, 1 - 6.02 dB (100%), 2 - 4.86 dB (87.5%), 3 - 3.52 dB (75%)
constexpr int32_t ClipMax3 = 32768 * 75 / 100;
constexpr int32_t ClipMax2 = 32768 * 875 / 1000;
switch (m_cpl)
@ -364,8 +375,8 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
break;
case 1:
m_channels[ch].volume = data;
m_channels[ch].volume2 = 128;
m_channels[ch].volume2 = data;
m_channels[ch].volume = 128 << 17;
break;
case 2:
@ -373,7 +384,7 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
break;
case 3:
if (data & 6)
if ((data & 6) == 2 || ((data & 6) == 6 && !m_channels[ch].is_playing)) // both KON bits is 1 = "Keep Playing", do not restart channel in this case
{
uint8_t phrase = m_channels[ch].phrase;
m_channels[ch].atbl = m_rom[(4*phrase)+0] >> 4 & 7;
@ -382,7 +393,7 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
m_channels[ch].is_playing = true;
}
else
else if ((data & 6) == 0)
m_channels[ch].is_playing = false;
m_channels[ch].loop = (data & 1) ? 255 : 0;
@ -391,7 +402,7 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
}
// sequencer registers
else
else if (reg >= 0x80)
{
int ch = reg >> 4 & 0x07;
@ -401,22 +412,26 @@ void ymz770_device::internal_reg_write(uint8_t reg, uint8_t data)
m_sequences[ch].sequence = data;
break;
case 1:
if (data & 6)
if ((data & 6) == 2 || ((data & 6) == 6 && !m_sequences[ch].is_playing)) // both KON bits 1 is "Keep Playing"
{
m_sequences[ch].offset = get_seq_offs(m_sequences[ch].sequence);
m_sequences[ch].delay = 0;
m_sequences[ch].is_playing = true;
}
else
else if ((data & 6) == 0)
m_sequences[ch].is_playing = false;
m_sequences[ch].loop = data & 1;
break;
default:
if (data)
logerror("unimplemented write %02X %02X\n", reg, data);
break;
}
}
else
logerror("unimplemented write %02X %02X\n", reg, data);
}
//-------------------------------------------------
@ -428,6 +443,18 @@ ymz774_device::ymz774_device(const machine_config &mconfig, const char *tag, dev
{
}
// volume increments, fractions of 0x20000, likely typical for Yamaha log-linear
static const uint32_t volinc[256] = {
0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,
32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,
64,66,68,70,72,74,76,78,80,82,84,86,88,90,92,94,96,98,100,102,104,106,108,110,112,114,116,118,120,122,124,126,
128,132,136,140,144,148,152,156,160,164,168,172,176,180,184,188,192,196,200,204,208,212,216,220,224,228,232,236,240,244,248,252,
256,264,272,280,288,296,304,312,320,328,336,344,351,360,368,376,384,392,400,408,416,424,432,440,448,456,464,471,480,488,495,504,
511,528,543,559,576,592,608,624,639,656,671,688,703,719,736,752,767,783,799,815,831,847,863,879,895,910,928,942,958,975,991,1006,
1023,1054,1087,1119,1149,1181,1215,1247,1277,1312,1340,1373,1404,1436,1469,1504,1534,1566,1598,1626,1661,1691,1721,1753,1786,1820,1856,1883,1912,1951,1981,2013,
2045,2102,2174,2238,2292,2363,2423,2487,2553,2624,2679,2737,2797,2860,2926,2996,3068,3118,3197,3252,3308,3367,3427,3490,3555,3623,3694,3767,3804,3882,3963,4005
};
READ8_MEMBER(ymz774_device::read)
{
if (offset & 1)
@ -464,10 +491,9 @@ void ymz774_device::internal_reg_write(uint8_t reg, uint8_t data)
switch (reg & 0xf8)
{
case 0x10: // Volume 1
m_channels[ch].volume = data;
m_channels[ch].volume_target = data;
break;
case 0x18: // Volume 1 delayed transition
if (data) logerror("unimplemented write %02X %02X\n", reg, data);
m_channels[ch].volume_delay = data;
break;
case 0x20: // Volume 2
@ -499,12 +525,14 @@ void ymz774_device::internal_reg_write(uint8_t reg, uint8_t data)
m_channels[ch].last_block = false;
m_channels[ch].is_playing = true;
m_channels[ch].is_paused = false; // checkme, might be not needed
}
else
m_channels[ch].is_playing = false;
break;
case 0x58: // Pause / Resume
if (data) logerror("pause/resume unimplemented %02X %02X\n", reg, data);
m_channels[ch].is_paused = data ? true : false;
if (data) logerror("CHECKME: CHAN pause/resume %02X %02X\n", reg, data);
break;
}
}
@ -521,7 +549,7 @@ void ymz774_device::internal_reg_write(uint8_t reg, uint8_t data)
if (reg & 1)
m_sequences[sq].sequence = (m_sequences[sq].sequence & 0xff00) | data;
else
m_sequences[sq].sequence = (m_sequences[sq].sequence & 0x00ff) | ((data & 0x0f) << 8);
m_sequences[sq].sequence = (m_sequences[sq].sequence & 0x00ff) | ((data & 0x07) << 8);
break;
case 0x70: // Start / Stop
if (data)
@ -530,6 +558,7 @@ void ymz774_device::internal_reg_write(uint8_t reg, uint8_t data)
m_sequences[sq].offset = get_seq_offs(m_sequences[sq].sequence);
m_sequences[sq].delay = 0;
m_sequences[sq].is_playing = true;
m_sequences[sq].is_paused = false; // checkme, might be not needed
}
else
{
@ -542,7 +571,8 @@ void ymz774_device::internal_reg_write(uint8_t reg, uint8_t data)
}
break;
case 0x78: // Pause / Resume
if (data) logerror("SEQ pause/resume unimplemented %02X %02X\n", reg, data);
m_sequences[sq].is_paused = data ? true : false;
if (data) logerror("CHECKME: SEQ pause/resume %02X %02X\n", reg, data);
break;
case 0x80: // Loop count, 0 = off, 255 - infinite
m_sequences[sq].loop = data;
@ -603,25 +633,50 @@ void ymz774_device::internal_reg_write(uint8_t reg, uint8_t data)
else
{
switch (reg) {
case 0xd0:
case 0xd0: // Total Volume L0/R0
m_vlma = data;
break;
case 0xd1:
case 0xd1: // Total Volume L1/R1
m_vlma1 = data;
break;
case 0xd2:
case 0xd2: // Clip limit
m_cpl = data;
break;
//case 0xd3: // Digital/PWM output
//case 0xd4: // Digital audio IF/IS L0/R0
//case 0xd5: // Digital audio IF/IS L1/R1
//case 0xd8: // GPIO A
//case 0xdd: // GPIO B
//case 0xde: // GPIO C
case 0xf0:
m_bank = data & 1;
if (data > 1) logerror("Set bank %02X!\n", data);
break;
default:
logerror("unimplemented write %02X %02X\n", reg, data);
break;
}
}
}
void ymz774_device::sequencer()
{
for (auto & chan : m_channels)
{
if (chan.is_playing && !chan.is_paused && (chan.volume >> 17) != chan.volume_target)
{
if (chan.volume_delay)
{
if ((chan.volume >> 17) < chan.volume_target)
chan.volume += volinc[chan.volume_delay];
else
chan.volume -= volinc[chan.volume_delay];
}
else
chan.volume = chan.volume_target << 17;
}
}
for (int i = 0; i < 8; i++)
{
auto & sqc = m_sqcs[i];
@ -630,12 +685,13 @@ void ymz774_device::sequencer()
if (sqc.is_playing && !sqc.is_waiting)
{
// SQC consists of 4 byte records: SEQ num H, SEQ num L, SEQ Loop count, End flag (0xff)
sequence.sequence = ((get_rom_byte(sqc.offset) << 8) | get_rom_byte(sqc.offset + 1)) & 0xfff;
sequence.sequence = ((get_rom_byte(sqc.offset) << 8) | get_rom_byte(sqc.offset + 1)) & 0x7ff;
sqc.offset += 2;
sequence.loop = get_rom_byte(sqc.offset++);
sequence.offset = get_seq_offs(sequence.sequence);
sequence.delay = 0;
sequence.is_playing = true;
sequence.is_paused = false; // checkme, might be not needed
sqc.is_waiting = true;
if (get_rom_byte(sqc.offset++) == 0xff)
{
@ -650,7 +706,7 @@ void ymz774_device::sequencer()
}
}
if (sequence.is_playing)
if (sequence.is_playing && !sequence.is_paused)
{
if (sequence.delay > 0)
--sequence.delay;

View File

@ -79,12 +79,13 @@ protected:
uint8_t pan_delay;
uint8_t pan1;
uint8_t pan1_delay;
uint8_t volume;
int32_t volume;
uint8_t volume_target;
uint8_t volume_delay;
uint8_t volume2;
uint8_t loop;
bool is_playing, last_block;
bool is_playing, last_block, is_paused;
mpeg_audio *decoder;
@ -104,6 +105,7 @@ protected:
uint32_t offset;
uint8_t bank;
bool is_playing;
bool is_paused;
};
struct ymz_sqc
{

View File

@ -389,6 +389,7 @@ static ADDRESS_MAP_START( pgm2_map, AS_PROGRAM, 32, pgm2_state )
// internal IGS036 - most of them is standard ATMEL peripherals followed by custom bits
// AM_RANGE(0xffffec00, 0xffffec7f) SMC (Static Memory Controller)
// AM_RANGE(0xffffee00, 0xffffee57) MATRIX (Bus Matrix)
AM_RANGE(0xfffff000, 0xfffff14b) AM_DEVICE("arm_aic", arm_aic_device, regs_map)
// AM_RANGE(0xfffff200, 0xfffff247) DBGU (Debug Unit)
// AM_RANGE(0xfffff400, 0xfffff4af) PIO (Parallel Input Output Controller)