diff options
Diffstat (limited to 'src/devices/sound/rf5c400.cpp')
-rw-r--r-- | src/devices/sound/rf5c400.cpp | 216 |
1 files changed, 152 insertions, 64 deletions
diff --git a/src/devices/sound/rf5c400.cpp b/src/devices/sound/rf5c400.cpp index 6c047935399..b4311fc7c57 100644 --- a/src/devices/sound/rf5c400.cpp +++ b/src/devices/sound/rf5c400.cpp @@ -5,16 +5,6 @@ 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" @@ -23,10 +13,12 @@ namespace { int volume_table[256]; -double pan_table[0x64]; +double pan_table[256]; void init_static_tables() { + std::fill(std::begin(pan_table), std::end(pan_table), 0.0); + // init volume/pan tables double max = 255.0; for (int i = 0; i < 256; i++) { @@ -36,9 +28,6 @@ void init_static_tables() 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; - } } @@ -129,7 +118,7 @@ void rf5c400_device::envelope_tables::init(uint32_t clock) 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) - , device_rom_interface(mconfig, *this, 25, ENDIANNESS_LITTLE, 16) + , device_rom_interface(mconfig, *this) , m_stream(nullptr) , m_env_tables() { @@ -149,72 +138,95 @@ void rf5c400_device::device_start() // init channel info for (rf5c400_channel &chan : m_channels) { + chan.startH = 0; + chan.startL = 0; + chan.freq = 0; + chan.endL = 0; + chan.endHloopH = 0; + chan.loopL = 0; + chan.pan = 0; + chan.effect = 0; + chan.volume = 0; + chan.attack = 0; + chan.decay = 0; + chan.release = 0; + chan.pos = 0; + chan.step = 0; + chan.keyon = 0; chan.env_phase = PHASE_NONE; chan.env_level = 0.0; chan.env_step = 0.0; chan.env_scale = 1.0; } + m_req_channel = 0; + save_item(NAME(m_rf5c400_status)); save_item(NAME(m_ext_mem_address)); save_item(NAME(m_ext_mem_data)); + save_item(NAME(m_req_channel)); + + save_item(STRUCT_MEMBER(m_channels, startH)); + save_item(STRUCT_MEMBER(m_channels, startL)); + save_item(STRUCT_MEMBER(m_channels, freq)); + save_item(STRUCT_MEMBER(m_channels, endL)); + save_item(STRUCT_MEMBER(m_channels, endHloopH)); + save_item(STRUCT_MEMBER(m_channels, loopL)); + save_item(STRUCT_MEMBER(m_channels, pan)); + save_item(STRUCT_MEMBER(m_channels, effect)); + save_item(STRUCT_MEMBER(m_channels, volume)); + save_item(STRUCT_MEMBER(m_channels, attack)); + save_item(STRUCT_MEMBER(m_channels, decay)); + save_item(STRUCT_MEMBER(m_channels, release)); + save_item(STRUCT_MEMBER(m_channels, cutoff)); + save_item(STRUCT_MEMBER(m_channels, pos)); + save_item(STRUCT_MEMBER(m_channels, step)); + save_item(STRUCT_MEMBER(m_channels, keyon)); + save_item(STRUCT_MEMBER(m_channels, env_phase)); + save_item(STRUCT_MEMBER(m_channels, env_level)); + save_item(STRUCT_MEMBER(m_channels, env_step)); + save_item(STRUCT_MEMBER(m_channels, env_scale)); + + m_stream = stream_alloc(0, 4, clock() / 384); +} - 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); - } +//------------------------------------------------- +// device_clock_changed - called if the clock +// changes +//------------------------------------------------- - m_stream = stream_alloc(0, 2, clock() / 384); +void rf5c400_device::device_clock_changed() +{ + m_env_tables.init(clock()); + m_stream->set_sample_rate(clock() / 384); } //------------------------------------------------- // 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) +void rf5c400_device::sound_stream_update(sound_stream &stream) { int i, ch; - uint32_t end, loop; + uint64_t start, end, loop; uint64_t pos; - uint8_t vol, lvol, rvol, type; + uint8_t vol, lvol, rvol, effect_lvol, effect_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; + start = ((uint32_t)(channel->startH & 0xFF00) << 8) | channel->startL; + end = ((uint32_t)(channel->endHloopH & 0xFF) << 16) | channel->endL; + loop = ((uint32_t)(channel->endHloopH & 0xFF00) << 8) | channel->loopL; pos = channel->pos; vol = channel->volume & 0xFF; lvol = channel->pan & 0xFF; rvol = channel->pan >> 8; + effect_lvol = channel->effect & 0xFF; + effect_rvol = channel->effect >> 8; type = (channel->volume >> 8) & TYPE_MASK; env_phase = channel->env_phase; @@ -222,7 +234,13 @@ void rf5c400_device::sound_stream_update(sound_stream &stream, stream_sample_t * env_step = channel->env_step; env_rstep = env_step * channel->env_scale; - for (i=0; i < samples; i++) + if (start == end) + { + // This occurs in pop'n music when trying to play a non-existent sample on the sound test menu + continue; + } + + for (i=0; i < stream.samples(); i++) { int16_t tmp; int32_t sample; @@ -292,14 +310,25 @@ void rf5c400_device::sound_stream_update(sound_stream &stream, stream_sample_t * sample *= volume_table[vol]; sample = (sample >> 9) * env_level; - *buf0++ += sample * pan_table[lvol]; - *buf1++ += sample * pan_table[rvol]; + stream.add_int(0, i, sample * pan_table[lvol], 32768); + stream.add_int(1, i, sample * pan_table[rvol], 32768); + stream.add_int(2, i, sample * pan_table[effect_lvol], 32768); + stream.add_int(3, i, sample * pan_table[effect_rvol], 32768); pos += channel->step; if ((pos>>16) > end) { pos -= loop<<16; - pos &= 0xFFFFFF0000U; + pos &= 0xFFFFFF0000ULL; + + if (pos < (start<<16)) + { + // This case only shows up in Firebeat games from what I could tell. + // The loop value will be higher than the actual buffer size. + // This is used when DMAs will be overwriting the current buffer. + // It expects the buffer to be looped without any additional commands. + pos = start<<16; + } } } @@ -311,18 +340,18 @@ void rf5c400_device::sound_stream_update(sound_stream &stream, stream_sample_t * } } -void rf5c400_device::rom_bank_updated() +void rf5c400_device::rom_bank_pre_change() { m_stream->update(); } /*****************************************************************************/ -READ16_MEMBER( rf5c400_device::rf5c400_r ) +uint16_t rf5c400_device::rf5c400_r(offs_t offset, uint16_t mem_mask) { if (offset < 0x400) { - //osd_printf_debug("%s:rf5c400_r: %08X, %08X\n", machine().describe_context().c_str(), offset, mem_mask); + //osd_printf_debug("%s:rf5c400_r: %08X, %08X\n", machine().describe_context(), offset, mem_mask); switch(offset) { @@ -336,6 +365,32 @@ READ16_MEMBER( rf5c400_device::rf5c400_r ) return 0; } + case 0x09: // position read? + { + // The game will always call rf5c400_w 0x08 with a channel number and some other value before reading this register. + // The call to rf5c400_w 0x08 contains additional information, potentially what information it's expecting to be returned here. + // This implementation assumes all commands want the same information as command 6. + m_stream->update(); + + rf5c400_channel* channel = &m_channels[m_req_channel]; + if (channel->env_phase == PHASE_NONE) + { + return 0; + } + + // pop'n music's SPU program expects to read this register 6 times with the same value between every read before it will send the next DMA request. + // + // This register is polled while a streaming BGM is being played. + // For pop'n music specifically, the game starts off by reading 0x200000 into 0x00780000 - 0x00880000. + // When 2xxx is found (pos - start = 0x00080000), it will trigger the next DMA of 0x100000 overwriting 0x00780000 - 0x00800000, and continues polling the register until it reads 1xxx next. + // When 1xxx is found (pos - start = 0x00040000), it will trigger the next DMA of 0x100000 overwriting 0x00800000 - 0x00880000, and continues polling the register until it reads 2xxx next. + // ... repeat until song is finished, alternating between 2xxx and 1xxx ... + // This ends up so that it'll always be buffering new sample data into the sections of memory that aren't being used. + auto start = ((uint32_t)(channel->startH & 0xFF00) << 8) | channel->startL; + auto ch_offset = (channel->pos >> 16) - start; + return ch_offset >> 6; + } + case 0x13: // memory read { return read_word(m_ext_mem_address<<1); @@ -343,7 +398,7 @@ READ16_MEMBER( rf5c400_device::rf5c400_r ) default: { - //osd_printf_debug("%s:rf5c400_r: %08X, %08X\n", machine().describe_context().c_str(), offset, mem_mask); + //osd_printf_debug("%s:rf5c400_r: %08X, %08X\n", machine().describe_context(), offset, mem_mask); return 0; } } @@ -364,7 +419,7 @@ READ16_MEMBER( rf5c400_device::rf5c400_r ) } } -WRITE16_MEMBER( rf5c400_device::rf5c400_w ) +void rf5c400_device::rf5c400_w(offs_t offset, uint16_t data, uint16_t mem_mask) { if (offset < 0x400) { @@ -383,7 +438,7 @@ WRITE16_MEMBER( rf5c400_device::rf5c400_w ) { case 0x60: m_channels[ch].pos = - ((m_channels[ch].startH & 0xFF00) << 8) | m_channels[ch].startL; + ((uint32_t)(m_channels[ch].startH & 0xFF00) << 8) | m_channels[ch].startL; m_channels[ch].pos <<= 16; m_channels[ch].env_phase = PHASE_ATTACK; @@ -413,8 +468,16 @@ WRITE16_MEMBER( rf5c400_device::rf5c400_w ) break; } - case 0x08: // relative to env attack (channel no) - case 0x09: // relative to env attack (0x0c00/ 0x1c00) + case 0x08: + { + // There's some other data stuffed in the upper bits beyond the channel: data >> 5 + // The other data might be some kind of register or command. I've seen 0, 4, 5, and 6. + // Firebeat uses 6 when polling rf5c400_r 0x09. + m_req_channel = data & 0x1f; + break; + } + + case 0x09: // relative to env attack (0x0c00/ 0x1c00/ 0x1e00) case 0x11: // memory r/w address, bits 15 - 0 { @@ -458,11 +521,11 @@ WRITE16_MEMBER( rf5c400_device::rf5c400_w ) default: { - //osd_printf_debug("%s:rf5c400_w: %08X, %08X, %08X\n", machine().describe_context().c_str(), data, offset, mem_mask); + //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\n", machine().describe_context().c_str(), data, offset, mem_mask); + //osd_printf_debug("%s:rf5c400_w: %08X, %08X, %08X\n", machine().describe_context(), data, offset, mem_mask); } else { @@ -513,6 +576,31 @@ WRITE16_MEMBER( rf5c400_device::rf5c400_w ) case 0x07: // effect depth { // 0xCCRR: CC = chorus send depth, RR = reverb send depth + + // Note: beatmania III uses this register differently? + // When effects are off, it writes 0xe0e0 to this register: + // Changed ch 30 reg 6 to volume 18176 -> l: 71, r: 0 + // Changed ch 30 reg 7 to volume 57568 -> 0xe0e0 + // Changed ch 31 reg 6 to volume 71 -> l: 0, r: 71 + // Changed ch 31 reg 7 to volume 57568 -> 0xe0e0 + // When effects are on, it writes reg 6 information here: + // Changed ch 30 reg 6 to volume 57568 -> 0xe0e0 + // Changed ch 30 reg 7 to volume 18176 -> l: 71, r: 0 + // Changed ch 31 reg 6 to volume 57568 -> 0xe0e0 + // Changed ch 31 reg 7 to volume 71 -> l: 0, r: 71 + // + // I've observed values of 0xff instead of 0xe0 in some games like Gradius 4, + // so 0xe0 is not some kind of max value. + // + // When effects are enabled the audio is redirected to an external + // PCB board for effect processing before returning to the sound PCB. + // + // That makes me think that the effects are not internal and that + // this register is just used to adjust the volume of the channels + // being routed externally. + // + // Whether the effect is a chorus or reverb or something else would + // depend on how the PCB is using these external channels. channel->effect = data; break; } |