diff options
Diffstat (limited to 'trunk/src/emu/sound/k054539.c')
-rw-r--r-- | trunk/src/emu/sound/k054539.c | 525 |
1 files changed, 525 insertions, 0 deletions
diff --git a/trunk/src/emu/sound/k054539.c b/trunk/src/emu/sound/k054539.c new file mode 100644 index 00000000000..fbe32e56999 --- /dev/null +++ b/trunk/src/emu/sound/k054539.c @@ -0,0 +1,525 @@ +/********************************************************* + + Konami 054539 PCM Sound Chip + + A lot of information comes from Amuse. + Big thanks to them. + + + +CHANNEL_DEBUG enables the following keys: + + PAD. : toggle debug mode + PAD0 : toggle chip (0 / 1) + PAD4,6 : select channel (0 - 7) + PAD8,2 : adjust gain (00=0.0 10=1.0, 20=2.0, etc.) + PAD5 : reset gain factor to 1.0 + +*********************************************************/ + +#include "emu.h" +#include "k054539.h" + +const device_type K054539 = &device_creator<k054539_device>; + +#define CHANNEL_DEBUG 0 +#define VERBOSE 0 +#define LOG(x) do { if (VERBOSE) logerror x; } while (0) + +k054539_device::k054539_device(const machine_config &mconfig, const char *tag, device_t *owner, UINT32 clock) + : device_t(mconfig, K054539, "K054539", "k054539", tag, owner, clock), + device_sound_interface(mconfig, *this) +{ +} + + +//------------------------------------------------- +// static_set_interface - configuration helper +// to set the interface +//------------------------------------------------- + +void k054539_device::static_set_interface(device_t &device, const k054539_interface &interface) +{ + k054539_device &k = downcast<k054539_device &>(device); + static_cast<k054539_interface &>(k) = interface; +} + +/* Registers: + 00..ff: 20 bytes/channel, 8 channels + 00..02: pitch (lsb, mid, msb) + 03: volume (0=max, 0x40=-36dB) + 04: reverb volume (idem) + 05: pan (1-f right, 10 middle, 11-1f left) + 06..07: reverb delay (0=max, current computation non-trusted) + 08..0a: loop (lsb, mid, msb) + 0c..0e: start (lsb, mid, msb) (and current position ?) + + 100.1ff: effects? + 13f: pan of the analog input (1-1f) + + 200..20f: 2 bytes/channel, 8 channels + 00: type (b2-3), reverse (b5) + 01: loop (b0) + + 214: keyon (b0-7 = channel 0-7) + 215: keyoff "" + 22c: channel active? "" + 22d: data read/write port + 22e: rom/ram select (00..7f == rom banks, 80 = ram) + 22f: enable pcm (b0), disable register ram updating (b7) + + The chip has a 0x4000 bytes reverb buffer (the ram from 0x22e). + The reverb delay is actually an offset in this buffer. +*/ + +void k054539_device::init_flags(int _flags) +{ + flags = _flags; +} + +void k054539_device::set_gain(int channel, double _gain) +{ + if(_gain >= 0) + gain[channel] = _gain; +} +//* + +bool k054539_device::regupdate() +{ + return !(regs[0x22f] & 0x80); +} + +void k054539_device::keyon(int channel) +{ + if(regupdate()) + regs[0x22c] |= 1 << channel; +} + +void k054539_device::keyoff(int channel) +{ + if(regupdate()) + regs[0x22c] &= ~(1 << channel); +} + +void k054539_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +{ +#define VOL_CAP 1.80 + + static const INT16 dpcm[16] = { + 0<<8, 1<<8, 4<<8, 9<<8, 16<<8, 25<<8, 36<<8, 49<<8, + -64<<8, -49<<8, -36<<8, -25<<8, -16<<8, -9<<8, -4<<8, -1<<8 + }; + + + INT16 *rbase = (INT16 *)ram; + + if(!(regs[0x22f] & 1)) + return; + + for(int sample = 0; sample != samples; sample++) { + double lval, rval; + if(!(flags & DISABLE_REVERB)) + lval = rval = rbase[reverb_pos]; + else + lval = rval = 0; + rbase[reverb_pos] = 0; + + for(int ch=0; ch<8; ch++) + if(regs[0x22c] & (1<<ch)) { + unsigned char *base1 = regs + 0x20*ch; + unsigned char *base2 = regs + 0x200 + 0x2*ch; + channel *chan = channels + ch; + + int delta = base1[0x00] | (base1[0x01] << 8) | (base1[0x02] << 16); + + int vol = base1[0x03]; + + int bval = vol + base1[0x04]; + if (bval > 255) + bval = 255; + + int pan = base1[0x05]; + // DJ Main: 81-87 right, 88 middle, 89-8f left + if (pan >= 0x81 && pan <= 0x8f) + pan -= 0x81; + else if (pan >= 0x11 && pan <= 0x1f) + pan -= 0x11; + else + pan = 0x18 - 0x11; + + double cur_gain = gain[ch]; + + double lvol = voltab[vol] * pantab[pan] * cur_gain; + if (lvol > VOL_CAP) + lvol = VOL_CAP; + + double rvol = voltab[vol] * pantab[0xe - pan] * cur_gain; + if (rvol > VOL_CAP) + rvol = VOL_CAP; + + double rbvol= voltab[bval] * cur_gain / 2; + if (rbvol > VOL_CAP) + rbvol = VOL_CAP; + + int rdelta = (base1[6] | (base1[7] << 8)) >> 3; + rdelta = (rdelta + reverb_pos) & 0x3fff; + + int cur_pos = (base1[0x0c] | (base1[0x0d] << 8) | (base1[0x0e] << 16)) & rom_mask; + + int fdelta, pdelta; + if(base2[0] & 0x20) { + delta = -delta; + fdelta = +0x10000; + pdelta = -1; + } else { + fdelta = -0x10000; + pdelta = +1; + } + + int cur_pfrac, cur_val, cur_pval; + if(cur_pos != chan->pos) { + chan->pos = cur_pos; + cur_pfrac = 0; + cur_val = 0; + cur_pval = 0; + } else { + cur_pfrac = chan->pfrac; + cur_val = chan->val; + cur_pval = chan->pval; + } + + switch(base2[0] & 0xc) { + case 0x0: { // 8bit pcm + cur_pfrac += delta; + while(cur_pfrac & ~0xffff) { + cur_pfrac += fdelta; + cur_pos += pdelta; + + cur_pval = cur_val; + cur_val = (INT16)(rom[cur_pos] << 8); + if(cur_val == (INT16)0x8000 && (base2[1] & 1)) { + cur_pos = (base1[0x08] | (base1[0x09] << 8) | (base1[0x0a] << 16)) & rom_mask; + cur_val = (INT16)(rom[cur_pos] << 8); + } + if(cur_val == (INT16)0x8000) { + keyoff(ch); + cur_val = 0; + break; + } + } + break; + } + + case 0x4: { // 16bit pcm lsb first + pdelta <<= 1; + + cur_pfrac += delta; + while(cur_pfrac & ~0xffff) { + cur_pfrac += fdelta; + cur_pos += pdelta; + + cur_pval = cur_val; + cur_val = (INT16)(rom[cur_pos] | rom[cur_pos+1]<<8); + if(cur_val == (INT16)0x8000 && (base2[1] & 1)) { + cur_pos = (base1[0x08] | (base1[0x09] << 8) | (base1[0x0a] << 16)) & rom_mask; + cur_val = (INT16)(rom[cur_pos] | rom[cur_pos+1]<<8); + } + if(cur_val == (INT16)0x8000) { + keyoff(ch); + cur_val = 0; + break; + } + } + break; + } + + case 0x8: { // 4bit dpcm + cur_pos <<= 1; + cur_pfrac <<= 1; + if(cur_pfrac & 0x10000) { + cur_pfrac &= 0xffff; + cur_pos |= 1; + } + + cur_pfrac += delta; + while(cur_pfrac & ~0xffff) { + cur_pfrac += fdelta; + cur_pos += pdelta; + + cur_pval = cur_val; + cur_val = rom[cur_pos>>1]; + if(cur_val == 0x88 && (base2[1] & 1)) { + cur_pos = ((base1[0x08] | (base1[0x09] << 8) | (base1[0x0a] << 16)) & rom_mask) << 1; + cur_val = rom[cur_pos>>1]; + } + if(cur_val == 0x88) { + keyoff(ch); + cur_val = 0; + break; + } + if(cur_pos & 1) + cur_val >>= 4; + else + cur_val &= 15; + cur_val = cur_pval + dpcm[cur_val]; + if(cur_val < -32768) + cur_val = -32768; + else if(cur_val > 32767) + cur_val = 32767; + } + + cur_pfrac >>= 1; + if(cur_pos & 1) + cur_pfrac |= 0x8000; + cur_pos >>= 1; + break; + } + default: + LOG(("Unknown sample type %x for channel %d\n", base2[0] & 0xc, ch)); + break; + } + lval += cur_val * lvol; + rval += cur_val * rvol; + rbase[(rdelta + reverb_pos) & 0x1fff] += INT16(cur_val*rbvol); + + chan->pos = cur_pos; + chan->pfrac = cur_pfrac; + chan->pval = cur_pval; + chan->val = cur_val; + + if(regupdate()) { + base1[0x0c] = cur_pos & 0xff; + base1[0x0d] = cur_pos>> 8 & 0xff; + base1[0x0e] = cur_pos>>16 & 0xff; + } + } + reverb_pos = (reverb_pos + 1) & 0x1fff; + outputs[0][sample] = INT16(lval); + outputs[1][sample] = INT16(rval); + } +} + + +void k054539_device::device_timer(emu_timer &timer, device_timer_id id, int param, void *ptr) +{ + if(regs[0x22f] & 0x20) + irq(this); +} + +void k054539_device::init_chip() +{ + memset(regs, 0, sizeof(regs)); + memset(posreg_latch, 0, sizeof(posreg_latch)); //* + flags |= UPDATE_AT_KEYON; //* make it default until proven otherwise + + ram = auto_alloc_array(machine(), unsigned char, 0x4000); + reverb_pos = 0; + cur_ptr = 0; + memset(ram, 0, 0x4000); + + memory_region *reg = (rgnoverride != NULL) ? machine().root_device().memregion(rgnoverride) : region(); + rom = *reg; + rom_size = reg->bytes(); + rom_mask = 0xffffffffU; + for(int i=0; i<32; i++) + if((1U<<i) >= rom_size) { + rom_mask = (1U<<i) - 1; + break; + } + + if(irq) { + // One or more of the registers must be the timer period + // And anyway, this particular frequency is probably wrong + // 480 hz is TRUSTED by gokuparo disco stage - the looping sample doesn't line up otherwise + emu_timer *tm = timer_alloc(); + tm->adjust(attotime::from_hz(480), 0, attotime::from_hz(480)); + } + + // If the clock is anything else than 48000, things are going to go wrong. + stream = stream_alloc(0, 2, clock()); + + save_item(NAME(regs)); + save_pointer(NAME(ram), 0x4000); + save_item(NAME(cur_ptr)); +} + +WRITE8_MEMBER(k054539_device::write) +{ + if(0) { + int voice, reg; + + /* The K054539 has behavior like many other wavetable chips including + the Ensoniq 550x and Gravis GF-1: if a voice is active, writing + to it's current position is silently ignored. + + Dadandaan depends on this or the vocals go wrong. + */ + if (offset < 8*0x20) + { + voice = offset / 0x20; + reg = offset & ~0x20; + + if(regs[0x22c] & (1<<voice)) + if (reg >= 0xc && reg <= 0xe) + return; + } + } + + bool latch = (flags & UPDATE_AT_KEYON) && (regs[0x22f] & 1); + + if (latch && offset < 0x100) + { + int offs = (offset & 0x1f) - 0xc; + int ch = offset >> 5; + + if (offs >= 0 && offs <= 2) + { + // latch writes to the position index registers + posreg_latch[ch][offs] = data; + return; + } + } + + else + switch(offset) { + case 0x13f: { + int pan = data >= 0x11 && data <= 0x1f ? data - 0x11 : 0x18 - 0x11; + if(apan) + apan(this, pantab[pan], pantab[0xe - pan]); + break; + } + + case 0x214: + if (latch) + { + for(int ch=0; ch<8; ch++) + { + if(data & (1<<ch)) + { + UINT8 *posptr = &posreg_latch[ch][0]; + UINT8 *regptr = regs + (ch<<5) + 0xc; + + // update the chip at key-on + regptr[0] = posptr[0]; + regptr[1] = posptr[1]; + regptr[2] = posptr[2]; + + keyon(ch); + } + } + } + else + { + for(int ch=0; ch<8; ch++) + if(data & (1<<ch)) + keyon(ch); + } + break; + + case 0x215: + for(int ch=0; ch<8; ch++) + if(data & (1<<ch)) + keyoff(ch); + break; + + case 0x22d: + if(regs[0x22e] == 0x80) + cur_zone[cur_ptr] = data; + cur_ptr++; + if(cur_ptr == cur_limit) + cur_ptr = 0; + break; + + case 0x22e: + cur_zone = + data == 0x80 ? ram : + rom + 0x20000*data; + cur_limit = data == 0x80 ? 0x4000 : 0x20000; + cur_ptr = 0; + break; + + default: +#if 0 + if(regs[offset] != data) { + if((offset & 0xff00) == 0) { + chanoff = offset & 0x1f; + if(chanoff < 4 || chanoff == 5 || + (chanoff >=8 && chanoff <= 0xa) || + (chanoff >= 0xc && chanoff <= 0xe)) + break; + } + if(1 || ((offset >= 0x200) && (offset <= 0x210))) + break; + logerror("K054539 %03x = %02x\n", offset, data); + } +#endif + break; + } + + regs[offset] = data; +} + +void k054539_device::reset_zones() +{ + int data = regs[0x22e]; + cur_zone = data == 0x80 ? ram : rom + 0x20000*data; + cur_limit = data == 0x80 ? 0x4000 : 0x20000; +} + +READ8_MEMBER(k054539_device::read) +{ + switch(offset) { + case 0x22d: + if(regs[0x22f] & 0x10) { + UINT8 res = cur_zone[cur_ptr]; + cur_ptr++; + if(cur_ptr == cur_limit) + cur_ptr = 0; + return res; + } else + return 0; + case 0x22c: + break; + default: + LOG(("K054539 read %03x\n", offset)); + break; + } + return regs[offset]; +} + +void k054539_device::device_start() +{ + for (int i = 0; i < 8; i++) + gain[i] = 1.0; + flags = RESET_FLAGS; + + /* + I've tried various equations on volume control but none worked consistently. + The upper four channels in most MW/GX games simply need a significant boost + to sound right. For example, the bass and smash sound volumes in Violent Storm + have roughly the same values and the voices in Tokimeki Puzzledama are given + values smaller than those of the hihats. Needless to say the two K054539 chips + in Mystic Warriors are completely out of balance. Rather than forcing a + "one size fits all" function to the voltab the current invert exponential + appraoch seems most appropriate. + */ + // Factor the 1/4 for the number of channels in the volume (1/8 is too harsh, 1/2 gives clipping) + // vol=0 -> no attenuation, vol=0x40 -> -36dB + for(int i=0; i<256; i++) + voltab[i] = pow(10.0, (-36.0 * (double)i / (double)0x40) / 20.0) / 4.0; + + // Pan table for the left channel + // Right channel is identical with inverted index + // Formula is such that pan[i]**2+pan[0xe-i]**2 = 1 (constant output power) + // and pan[0xe] = 1 (full panning) + for(int i=0; i<0xf; i++) + pantab[i] = sqrt((double)i) / sqrt((double)0xe); + + init_chip(); + + machine().save().register_postload(save_prepost_delegate(FUNC(k054539_device::reset_zones), this)); +} + +void k054539_device::device_reset() +{ +} |