diff options
Diffstat (limited to 'src/devices/sound/zsg2.cpp')
-rw-r--r-- | src/devices/sound/zsg2.cpp | 219 |
1 files changed, 110 insertions, 109 deletions
diff --git a/src/devices/sound/zsg2.cpp b/src/devices/sound/zsg2.cpp index bac1d065ba6..c1a93d874ca 100644 --- a/src/devices/sound/zsg2.cpp +++ b/src/devices/sound/zsg2.cpp @@ -1,91 +1,93 @@ // license:BSD-3-Clause // copyright-holders:Olivier Galibert, R. Belmont, hap, superctr /* - ZOOM ZSG-2 custom wavetable synthesizer - - Written by Olivier Galibert - MAME conversion by R. Belmont - Working emulation by The Talentuous Hands Of The Popularious hap - Properly working emulation by superctr - --------------------------------------------------------- - - Register map: - 000-5fe : Channel specific registers (48 channels) - (high) (low) - +000 : xxxxxxxx -------- : Start address (low) - +000 : -------- xxxxxxxx : Unknown register (usually cleared) - +002 : xxxxxxxx -------- : Address page - : -------- xxxxxxxx : Start address (high) - +004 : -------- -------- : Unknown register (usually cleared) - +006 : -----x-- -------- : Unknown bit, always set - +008 : xxxxxxxx xxxxxxxx : Frequency - +00a : xxxxxxxx -------- : DSP ch 3 (right) output gain - : -------- xxxxxxxx : Loop address (low) - +00c : xxxxxxxx xxxxxxxx : End address - +00e : xxxxxxxx -------- : DSP ch 2 (Left) output gain - : -------- xxxxxxxx : Loop address (high) - +010 : xxxxxxxx xxxxxxxx : Initial filter time constant - +012 : xxxxxxxx xxxxxxxx : Current filter time constant - +014 : xxxxxxxx xxxxxxxx : Initial volume - +016 : xxxxxxxx xxxxxxxx : Current volume? - +018 : xxxxxxxx xxxxxxxx : Target filter time constant - +01a : xxxxxxxx -------- : DSP ch 1 (chorus) output gain - : -------- xxxxxxxx : Filter ramping speed - +01c : xxxxxxxx xxxxxxxx : Target volume - +01e : xxxxxxxx -------- : DSP ch 0 (reverb) output gain - : -------- xxxxxxxx : Filter ramping speed - 600-604 : Key on flags (each bit corresponds to a channel) - 608-60c : Key off flags (each bit corresponds to a channel) - 618 : Unknown register (usually 0x5cbc is written) - 61a : Unknown register (usually 0x5cbc is written) - 620 : Unknown register (usually 0x0128 is written) - 628 : Unknown register (usually 0x0066 is written) - 630 : Unknown register (usually 0x0001 is written) - 638 : ROM readback address low - 63a : ROM readback address high - 63c : ROM readback word low - 63e : ROM readback word high - - --------------------------------------------------------- - - Additional notes on the sample format, reverse-engineered - by Olivier Galibert and David Haywood: - - The zoom sample rom is decomposed in 0x40000 bytes pages. Each page - starts by a header and is followed by compressed samples. - - The header is a vector of 16 bytes structures composed of 4 32bits - little-endian values representing: - - sample start position in bytes, always a multiple of 4 - - sample end position in bytes, minus 4, always... - - loop position in bytes, always.... - - flags, probably - - It is interesting to note that this header is *not* parsed by the - ZSG. The main program reads the rom through appropriate ZSG - commands, and use the results in subsequent register setups. It's - not even obvious that the ZSG cares about the pages, it may just - see the address space as linear. In the same line, the - interpretation of the flags is obviously dependent on the main - program, not the ZSG, but some of the bits are directly copied to - some of the registers. - - The samples are compressed with a 2:1 ratio. Each block of 4-bytes - becomes 4 16-bits samples. Reading the 4 bytes as a *little-endian* - 32bits values, the structure is: - - 42222222 51111111 60000000 ssss3333 - - 's' is a 4-bit scale value. '0000000', '1111111', '2222222' and - '6543333' are signed 7-bits values corresponding to the 4 samples. - To compute the final 16bits value, left-align and shift right by s. - Yes, that simple. - - --------------------------------------------------------- + +ZOOM ZSG-2 custom wavetable synthesizer + +Written by Olivier Galibert +MAME conversion by R. Belmont +Working emulation by The Talentuous Hands Of The Popularious hap +Properly working emulation by superctr +--------------------------------------------------------- + +Register map: +000-5fe : Channel specific registers (48 channels) + (high) (low) + +000 : xxxxxxxx -------- : Start address (low) + +000 : -------- xxxxxxxx : Unknown register (usually cleared) + +002 : xxxxxxxx -------- : Address page + : -------- xxxxxxxx : Start address (high) + +004 : -------- -------- : Unknown register (usually cleared) + +006 : -----x-- -------- : Unknown bit, always set + +008 : xxxxxxxx xxxxxxxx : Frequency + +00a : xxxxxxxx -------- : DSP ch 3 (right) output gain + : -------- xxxxxxxx : Loop address (low) + +00c : xxxxxxxx xxxxxxxx : End address + +00e : xxxxxxxx -------- : DSP ch 2 (Left) output gain + : -------- xxxxxxxx : Loop address (high) + +010 : xxxxxxxx xxxxxxxx : Initial filter time constant + +012 : xxxxxxxx xxxxxxxx : Current filter time constant + +014 : xxxxxxxx xxxxxxxx : Initial volume + +016 : xxxxxxxx xxxxxxxx : Current volume? + +018 : xxxxxxxx xxxxxxxx : Target filter time constant + +01a : xxxxxxxx -------- : DSP ch 1 (chorus) output gain + : -------- xxxxxxxx : Filter ramping speed + +01c : xxxxxxxx xxxxxxxx : Target volume + +01e : xxxxxxxx -------- : DSP ch 0 (reverb) output gain + : -------- xxxxxxxx : Filter ramping speed +600-604 : Key on flags (each bit corresponds to a channel) +608-60c : Key off flags (each bit corresponds to a channel) +618 : Unknown register (usually 0x5cbc is written) +61a : Unknown register (usually 0x5cbc is written) +620 : Unknown register (usually 0x0128 is written) +628 : Unknown register (usually 0x0066 is written) +630 : Unknown register (usually 0x0001 is written) +638 : ROM readback address low +63a : ROM readback address high +63c : ROM readback word low +63e : ROM readback word high + +--------------------------------------------------------- + +Additional notes on the sample format, reverse-engineered +by Olivier Galibert and David Haywood: + +The zoom sample rom is decomposed in 0x40000 bytes pages. Each page +starts by a header and is followed by compressed samples. + +The header is a vector of 16 bytes structures composed of 4 32bits +little-endian values representing: +- sample start position in bytes, always a multiple of 4 +- sample end position in bytes, minus 4, always... +- loop position in bytes, always.... +- flags, probably + +It is interesting to note that this header is *not* parsed by the +ZSG. The main program reads the rom through appropriate ZSG +commands, and use the results in subsequent register setups. It's +not even obvious that the ZSG cares about the pages, it may just +see the address space as linear. In the same line, the +interpretation of the flags is obviously dependent on the main +program, not the ZSG, but some of the bits are directly copied to +some of the registers. + +The samples are compressed with a 2:1 ratio. Each block of 4-bytes +becomes 4 16-bits samples. Reading the 4 bytes as a *little-endian* +32bits values, the structure is: + +42222222 51111111 60000000 ssss3333 + +'s' is a 4-bit scale value. '0000000', '1111111', '2222222' and +'6543333' are signed 7-bits values corresponding to the 4 samples. +To compute the final 16bits value, left-align and shift right by s. +Yes, that simple. + +--------------------------------------------------------- TODO: - Filter and ramping behavior might not be perfect. -- clicking / popping noises in gdarius, raystorm: maybe the sample ROMs are bad dumps? +- clicking / popping noises in gdarius, raystorm: maybe the sample ROMs + are bad dumps? - memory reads out of range sometimes */ @@ -120,7 +122,7 @@ zsg2_device::zsg2_device(const machine_config &mconfig, const char *tag, device_ , device_sound_interface(mconfig, *this) , m_mem_base(*this, DEVICE_SELF) , m_read_address(0) - , m_ext_read_handler(*this) + , m_ext_read_handler(*this, 0) { } @@ -130,8 +132,6 @@ zsg2_device::zsg2_device(const machine_config &mconfig, const char *tag, device_ void zsg2_device::device_start() { - m_ext_read_handler.resolve(); - memset(&m_chan, 0, sizeof(m_chan)); m_stream = stream_alloc(0, 4, clock() / 768); @@ -227,7 +227,7 @@ uint32_t zsg2_device::read_memory(uint32_t offset) if (offset >= m_mem_blocks) return 0; - if (m_ext_read_handler.isnull()) + if (m_ext_read_handler.isunset()) return m_mem_base[offset]; return m_ext_read_handler(offset); @@ -275,7 +275,7 @@ void zsg2_device::filter_samples(zchan *ch) ch->emphasis_filter_state += raw_samples[i]-((ch->emphasis_filter_state+EMPHASIS_ROUNDING)>>EMPHASIS_FILTER_SHIFT); int32_t sample = ch->emphasis_filter_state >> EMPHASIS_OUTPUT_SHIFT; - ch->samples[i+1] = std::min<int32_t>(std::max<int32_t>(sample, -32768), 32767); + ch->samples[i+1] = std::clamp<int32_t>(sample, -32768, 32767); } } @@ -283,16 +283,16 @@ void zsg2_device::filter_samples(zchan *ch) // sound_stream_update - handle a stream update //------------------------------------------------- -void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples) +void zsg2_device::sound_stream_update(sound_stream &stream) { - for (int i = 0; i < samples; i++) + for (int i = 0; i < stream.samples(); i++) { int32_t mix[4] = {}; // loop over all channels for (auto & elem : m_chan) { - if(~elem.status & STATUS_ACTIVE) + if (~elem.status & STATUS_ACTIVE) continue; elem.step_ptr += elem.step; @@ -311,7 +311,7 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in } } - if(elem.cur_pos == elem.start_pos) + if (elem.cur_pos == elem.start_pos) elem.emphasis_filter_state = EMPHASIS_INITIAL_BIAS; elem.step_ptr &= 0xffff; @@ -322,19 +322,19 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in int32_t sample = elem.samples[sample_pos]; // linear interpolation (hardware certainly does something similar) - sample += ((uint16_t)(elem.step_ptr<<2&0xffff) * (int16_t)(elem.samples[sample_pos+1] - sample))>>16; + sample += (uint16_t(elem.step_ptr << 2 & 0xffff) * int16_t(elem.samples[sample_pos+1] - sample)) >> 16; // another filter... - elem.output_filter_state += (sample - (elem.output_filter_state>>16)) * elem.output_cutoff; + elem.output_filter_state += (sample - (elem.output_filter_state >> 16)) * elem.output_cutoff; sample = elem.output_filter_state >> 16; // To prevent DC bias, we need to slowly discharge the filter when the output filter cutoff is 0 - if(!elem.output_cutoff) + if (!elem.output_cutoff) elem.output_filter_state >>= 1; - sample = (sample * elem.vol)>>16; + sample = (sample * elem.vol) >> 16; - for(int output=0; output<4; output++) + for (int output = 0; output < 4; output++) { int output_gain = elem.output_gain[output] & 0x1f; // left / right int32_t output_sample = sample; @@ -342,21 +342,20 @@ void zsg2_device::sound_stream_update(sound_stream &stream, stream_sample_t **in if (elem.output_gain[output] & 0x80) // perhaps ? output_sample = -output_sample; - mix[output] += (output_sample * m_gain_tab[output_gain&0x1f]) >> 16; + mix[output] += (output_sample * m_gain_tab[output_gain & 0x1f]) >> 16; } // Apply ramping every other update // It's possible key on is handled on the other sample - if(m_sample_count & 1) + if (m_sample_count & 1) { elem.vol = ramp(elem.vol, elem.vol_target, elem.vol_delta); elem.output_cutoff = ramp(elem.output_cutoff, elem.output_cutoff_target, elem.output_cutoff_delta); } } - for(int output=0; output<4; output++) - outputs[output][i] = std::min<int32_t>(std::max<int32_t>(mix[output], -32768), 32767); - + for (int output = 0; output < 4; output++) + stream.put_int_clamp(output, i, mix[output], 32768); } m_sample_count++; } @@ -494,17 +493,17 @@ uint16_t zsg2_device::chan_r(int ch, int reg) // calculate this value, for now I'm generating an opproximate inverse. int16_t zsg2_device::get_ramp(uint8_t val) { - int16_t frac = val<<12; // sign extend - frac = ((frac>>12) ^ 8) << (val >> 4); + int16_t frac = val << 12; // sign extend + frac = ((frac >> 12) ^ 8) << (val >> 4); return (frac >> 4); } inline uint16_t zsg2_device::ramp(uint16_t current, uint16_t target, int16_t delta) { int32_t rampval = current + delta; - if(delta < 0 && rampval < target) + if (delta < 0 && rampval < target) rampval = target; - else if(delta >= 0 && rampval > target) + else if (delta >= 0 && rampval > target) rampval = target; return rampval; @@ -575,7 +574,7 @@ void zsg2_device::control_w(int reg, uint16_t data) break; default: - if(reg < 0x20) + if (reg < 0x20) m_reg[reg] = data; logerror("ZSG2 control %02X = %04X\n", reg, data & 0xffff); break; @@ -599,7 +598,7 @@ uint16_t zsg2_device::control_r(int reg) return read_memory(m_read_address) >> 16; default: - if(reg < 0x20) + if (reg < 0x20) return m_reg[reg]; break; } @@ -609,7 +608,7 @@ uint16_t zsg2_device::control_r(int reg) /******************************************************************************/ -WRITE16_MEMBER(zsg2_device::write) +void zsg2_device::write(offs_t offset, uint16_t data, uint16_t mem_mask) { // we only support full 16-bit accesses if (mem_mask != 0xffff) @@ -633,7 +632,7 @@ WRITE16_MEMBER(zsg2_device::write) } } -READ16_MEMBER(zsg2_device::read) +uint16_t zsg2_device::read(offs_t offset, uint16_t mem_mask) { // we only support full 16-bit accesses if (mem_mask != 0xffff) @@ -642,6 +641,8 @@ READ16_MEMBER(zsg2_device::read) return 0; } + m_stream->update(); + if (offset < 0x300) { int chan = offset >> 4; |