summaryrefslogblamecommitdiffstatshomepage
path: root/src/devices/sound/qsoundhle.cpp
blob: 7cfe01391f42e9e3c6322d71ff3b6c59ac5bda8f (plain) (tree)
1
2
3
4
5
6
7
8
9
                       
                                          

                                                                            
                             

                             
                                                
 
                                 






                                                                            
                      
 


                    


                                                                               






                                                                                                                      
 







                                                                            
                                                                                                                     


                                                          
                           

                                 


 
                                                   







                                                   





                                                   
                                                                                                                
 
                            
 












                                                          
 










                                                            
 











                                                   
         






















                                                                 
         







                                         

 








                                                                  

                                                   




                                                   



                                

 
                                                   





                                                                                                                                   

                                            
 
                                          
         


                                         








                                          
                                                                             


                              
                                                                      



                                           
                                                       










                                                                                                         




























                                                                                                                            
                                                                 































                                                                                                             

 




                                                                       
                                                                       





















                                                                      
 

                                    
 
                                                    
 

                                                                  
         


                                       
         
                                      
         

                                  
         









                                                                                  
         



















                                                                                  


            










                                                          

         













                                                
         

                                              
 










                                                                                            
         







                                                                                                     
 

                                                                                                             
         
 













                                                                                          
 




































                                                                                                        
         





























































                                                                                         
                                                                                                                                     







                                                                                     

                                                                





                                                                                         

                                                                                                      
                 



                                                                                                







                                                                   
                                                                               

                                     

                                                                                                  





























































                                                                           
 
// license:BSD-3-Clause
// copyright-holders:superctr, Valley Bell
/***************************************************************************

  Capcom QSound DL-1425 (HLE)
  ===========================

  Driver by superctr with thanks to Valley Bell.

  Based on disassembled DSP code.

  Links:
  https://siliconpr0n.org/map/capcom/dl-1425

***************************************************************************/

#include "emu.h"
#include "qsoundhle.h"

#include <algorithm>
#include <limits>

// device type definition
DEFINE_DEVICE_TYPE(QSOUND_HLE, qsound_hle_device, "qsound_hle", "QSound (HLE)")

// DSP internal ROM region
ROM_START( qsound_hle )
	ROM_REGION16_LE( 0x2000, "dsp", 0 )
	// removing WORD_SWAP from original definition
	ROM_LOAD16_WORD( "dl-1425.bin", 0x0000, 0x2000, CRC(d6cf5ef5) SHA1(555f50fe5cdf127619da7d854c03f4a244a0c501) )
	ROM_IGNORE( 0x4000 )
ROM_END

//**************************************************************************
//  LIVE DEVICE
//**************************************************************************

//-------------------------------------------------
//  qsound_hle_device - constructor
//-------------------------------------------------

qsound_hle_device::qsound_hle_device(const machine_config &mconfig, const char *tag, device_t *owner, uint32_t clock)
	: device_t(mconfig, QSOUND_HLE, tag, owner, clock)
	, device_sound_interface(mconfig, *this)
	, device_rom_interface(mconfig, *this, 24)
	, m_stream(nullptr)
	, m_dsp_rom(*this, "dsp")
	, m_data_latch(0)
{
}

//-------------------------------------------------
//  rom_bank_updated - the rom bank has changed
//-------------------------------------------------

void qsound_hle_device::rom_bank_updated()
{
	m_stream->update();
}

//-------------------------------------------------
//  device_start - device-specific startup
//-------------------------------------------------

void qsound_hle_device::device_start()
{
	m_stream = stream_alloc(0, 2, clock() / 2 / 1248); // DSP program uses 1248 machine cycles per iteration

	init_register_map();

	// state save
	// PCM registers
	for (int j = 0; j < 16; j++) // PCM voices
	{
		save_item(NAME(m_voice[j].m_bank), j);
		save_item(NAME(m_voice[j].m_addr), j);
		save_item(NAME(m_voice[j].m_phase), j);
		save_item(NAME(m_voice[j].m_rate), j);
		save_item(NAME(m_voice[j].m_loop_len), j);
		save_item(NAME(m_voice[j].m_end_addr), j);
		save_item(NAME(m_voice[j].m_volume), j);
		save_item(NAME(m_voice[j].m_echo), j);
	}

	for (int j = 0; j < 3; j++) // ADPCM voices
	{
		save_item(NAME(m_adpcm[j].m_start_addr), j);
		save_item(NAME(m_adpcm[j].m_end_addr), j);
		save_item(NAME(m_adpcm[j].m_bank), j);
		save_item(NAME(m_adpcm[j].m_volume), j);
		save_item(NAME(m_adpcm[j].m_flag), j);
		save_item(NAME(m_adpcm[j].m_cur_vol), j);
		save_item(NAME(m_adpcm[j].m_step_size), j);
		save_item(NAME(m_adpcm[j].m_cur_addr), j);
	}

	// PCM voices
	save_item(NAME(m_voice_pan));

	// QSound registers
	save_item(NAME(m_echo.m_end_pos));
	save_item(NAME(m_echo.m_feedback));
	save_item(NAME(m_echo.m_length));
	save_item(NAME(m_echo.m_last_sample));
	save_item(NAME(m_echo.m_delay_line));
	save_item(NAME(m_echo.m_delay_pos));

	for (int j = 0; j < 2; j++)  // left, right
	{
		save_item(NAME(m_filter[j].m_tap_count), j);
		save_item(NAME(m_filter[j].m_delay_pos), j);
		save_item(NAME(m_filter[j].m_table_pos), j);
		save_item(NAME(m_filter[j].m_taps), j);
		save_item(NAME(m_filter[j].m_delay_line), j);

		save_item(NAME(m_alt_filter[j].m_tap_count), j);
		save_item(NAME(m_alt_filter[j].m_delay_pos), j);
		save_item(NAME(m_alt_filter[j].m_table_pos), j);
		save_item(NAME(m_alt_filter[j].m_taps), j);
		save_item(NAME(m_alt_filter[j].m_delay_line), j);

		save_item(NAME(m_wet[j].m_delay), j);
		save_item(NAME(m_wet[j].m_volume), j);
		save_item(NAME(m_wet[j].m_write_pos), j);
		save_item(NAME(m_wet[j].m_read_pos), j);
		save_item(NAME(m_wet[j].m_delay_line), j);

		save_item(NAME(m_dry[j].m_delay), j);
		save_item(NAME(m_dry[j].m_volume), j);
		save_item(NAME(m_dry[j].m_write_pos), j);
		save_item(NAME(m_dry[j].m_read_pos), j);
		save_item(NAME(m_dry[j].m_delay_line), j);
	}

	save_item(NAME(m_state));
	save_item(NAME(m_next_state));
	save_item(NAME(m_delay_update));
	save_item(NAME(m_state_counter));
	save_item(NAME(m_ready_flag));
	save_item(NAME(m_data_latch));
	save_item(NAME(m_out));
}

//-------------------------------------------------
//  rom_region - return a pointer to the device's
//  internal ROM region
//-------------------------------------------------

const tiny_rom_entry *qsound_hle_device::device_rom_region() const
{
	return ROM_NAME( qsound_hle );
}

//-------------------------------------------------
//  device_reset - device-specific reset
//-------------------------------------------------

void qsound_hle_device::device_reset()
{
	m_ready_flag = 0;
	m_out[0] = m_out[1] = 0;
	m_state = STATE_BOOT;
	m_state_counter = 0;
}

//-------------------------------------------------
//  sound_stream_update - handle a stream update
//-------------------------------------------------

void qsound_hle_device::sound_stream_update(sound_stream &stream, stream_sample_t **inputs, stream_sample_t **outputs, int samples)
{
	// Clear the buffers
	std::fill_n(outputs[0], samples, 0);
	std::fill_n(outputs[1], samples, 0);

	for (int i = 0; i < samples; i ++)
	{
		update_sample();
		outputs[0][i] = m_out[0];
		outputs[1][i] = m_out[1];
	}
}


WRITE8_MEMBER(qsound_hle_device::qsound_w)
{
	switch (offset)
	{
		case 0:
			m_data_latch = (m_data_latch & 0x00ff) | (data << 8);
			break;

		case 1:
			m_data_latch = (m_data_latch & 0xff00) | data;
			break;

		case 2:
			m_stream->update();
			write_data(data, m_data_latch);
			break;

		default:
			logerror("%s: qsound_w %d = %02x\n", machine().describe_context(), offset, data);
			break;
	}
}


READ8_MEMBER(qsound_hle_device::qsound_r)
{
	// ready bit (0x00 = busy, 0x80 == ready)
	m_stream->update();
	return m_ready_flag;
}


void qsound_hle_device::write_data(uint8_t address, uint16_t data)
{
	uint16_t *destination = m_register_map[address];
	if (destination)
		*destination = data;
	m_ready_flag = 0;
}

void qsound_hle_device::init_register_map()
{
	// unused registers
	std::fill(std::begin(m_register_map), std::end(m_register_map), nullptr);

	// PCM registers
	for (int i = 0; i < 16; i++) // PCM voices
	{
		m_register_map[(i << 3) + 0] = (uint16_t*)&m_voice[(i + 1) % 16].m_bank; // Bank applies to the next channel
		m_register_map[(i << 3) + 1] = (uint16_t*)&m_voice[i].m_addr; // Current sample position and start position.
		m_register_map[(i << 3) + 2] = (uint16_t*)&m_voice[i].m_rate; // 4.12 fixed point decimal.
		m_register_map[(i << 3) + 3] = (uint16_t*)&m_voice[i].m_phase;
		m_register_map[(i << 3) + 4] = (uint16_t*)&m_voice[i].m_loop_len;
		m_register_map[(i << 3) + 5] = (uint16_t*)&m_voice[i].m_end_addr;
		m_register_map[(i << 3) + 6] = (uint16_t*)&m_voice[i].m_volume;
		m_register_map[(i << 3) + 7] = nullptr; // unused
		m_register_map[i + 0x80] = (uint16_t*)&m_voice_pan[i];
		m_register_map[i + 0xba] = (uint16_t*)&m_voice[i].m_echo;
	}

	// ADPCM registers
	for (int i = 0; i < 3; i++) // ADPCM voices
	{
		// ADPCM sample rate is fixed to 8khz. (one channel is updated every third sample)
		m_register_map[(i << 2) + 0xca] = (uint16_t*)&m_adpcm[i].m_start_addr;
		m_register_map[(i << 2) + 0xcb] = (uint16_t*)&m_adpcm[i].m_end_addr;
		m_register_map[(i << 2) + 0xcc] = (uint16_t*)&m_adpcm[i].m_bank;
		m_register_map[(i << 2) + 0xcd] = (uint16_t*)&m_adpcm[i].m_volume;
		m_register_map[i + 0xd6] = (uint16_t*)&m_adpcm[i].m_flag; // non-zero to start ADPCM playback
		m_register_map[i + 0x90] = (uint16_t*)&m_voice_pan[16 + i];
	}

	// QSound registers
	m_register_map[0x93] = (uint16_t*)&m_echo.m_feedback;
	m_register_map[0xd9] = (uint16_t*)&m_echo.m_end_pos;
	m_register_map[0xe2] = (uint16_t*)&m_delay_update; // non-zero to update delays
	m_register_map[0xe3] = (uint16_t*)&m_next_state;
	for (int i = 0; i < 2; i++)  // left, right
	{
		// Wet
		m_register_map[(i << 1) + 0xda] = (uint16_t*)&m_filter[i].m_table_pos;
		m_register_map[(i << 1) + 0xde] = (uint16_t*)&m_wet[i].m_delay;
		m_register_map[(i << 1) + 0xe4] = (uint16_t*)&m_wet[i].m_volume;
		// Dry
		m_register_map[(i << 1) + 0xdb] = (uint16_t*)&m_alt_filter[i].m_table_pos;
		m_register_map[(i << 1) + 0xdf] = (uint16_t*)&m_dry[i].m_delay;
		m_register_map[(i << 1) + 0xe5] = (uint16_t*)&m_dry[i].m_volume;
	}
}

int16_t qsound_hle_device::read_sample(uint16_t bank, uint16_t address)
{
	bank &= 0x7FFF;
	const uint32_t rom_addr = (bank << 16) | (address << 0);
	const uint8_t sample_data = read_byte(rom_addr);
	return (int16_t)(sample_data << 8); // bit0-7 is tied to ground
}

/********************************************************************/

// updates one DSP sample
void qsound_hle_device::update_sample()
{
	switch (m_state)
	{
		default:
		case STATE_INIT1:
		case STATE_INIT2:
			return state_init();
		case STATE_REFRESH1:
			return state_refresh_filter_1();
		case STATE_REFRESH2:
			return state_refresh_filter_2();
		case STATE_NORMAL1:
		case STATE_NORMAL2:
			return state_normal_update();
	}
}

// Initialization routine
void qsound_hle_device::state_init()
{
	int mode = (m_state == STATE_INIT2) ? 1 : 0;

	// we're busy for 4 samples, including the filter refresh.
	if (m_state_counter >= 2)
	{
		m_state_counter = 0;
		m_state = m_next_state;
		return;
	}
	else if (m_state_counter == 1)
	{
		m_state_counter++;
		return;
	}

	std::fill(std::begin(m_voice), std::end(m_voice), qsound_voice());
	std::fill(std::begin(m_adpcm), std::end(m_adpcm), qsound_adpcm());
	std::fill(std::begin(m_filter), std::end(m_filter), qsound_fir());
	std::fill(std::begin(m_alt_filter), std::end(m_alt_filter), qsound_fir());
	std::fill(std::begin(m_wet), std::end(m_wet), qsound_delay());
	std::fill(std::begin(m_dry), std::end(m_dry), qsound_delay());
	m_echo = qsound_echo();

	for (int i = 0; i < 19; i++)
	{
		m_voice_pan[i] = DATA_PAN_TAB + 0x10;
		m_voice_output[i] = 0;
	}

	for (int i = 0; i < 16; i++)
		m_voice[i].m_bank = 0x8000;
	for (int i = 0; i < 3; i++)
		m_adpcm[i].m_bank = 0x8000;

	if (mode == 0)
	{
		// mode 1
		m_wet[0].m_delay = 0;
		m_dry[0].m_delay = 46;
		m_wet[1].m_delay = 0;
		m_dry[1].m_delay = 48;
		m_filter[0].m_table_pos = DATA_FILTER_TAB + (FILTER_ENTRY_SIZE*1);
		m_filter[1].m_table_pos = DATA_FILTER_TAB + (FILTER_ENTRY_SIZE*2);
		m_echo.m_end_pos = DELAY_BASE_OFFSET + 6;
		m_next_state = STATE_REFRESH1;
	}
	else
	{
		// mode 2
		m_wet[0].m_delay = 1;
		m_dry[0].m_delay = 0;
		m_wet[1].m_delay = 0;
		m_dry[1].m_delay = 0;
		m_filter[0].m_table_pos = 0xf73;
		m_filter[1].m_table_pos = 0xfa4;
		m_alt_filter[0].m_table_pos = 0xf73;
		m_alt_filter[1].m_table_pos = 0xfa4;
		m_echo.m_end_pos = DELAY_BASE_OFFSET2 + 6;
		m_next_state = STATE_REFRESH2;
	}

	m_wet[0].m_volume = 0x3fff;
	m_dry[0].m_volume = 0x3fff;
	m_wet[1].m_volume = 0x3fff;
	m_dry[1].m_volume = 0x3fff;

	m_delay_update = 1;
	m_ready_flag = 0;
	m_state_counter = 1;
}

// Updates filter parameters for mode 1
void qsound_hle_device::state_refresh_filter_1()
{
	for (int ch = 0; ch < 2; ch++)
	{
		m_filter[ch].m_delay_pos = 0;
		m_filter[ch].m_tap_count = 95;

		for (int i = 0; i < 95; i++)
			m_filter[ch].m_taps[i] = read_dsp_rom(m_filter[ch].m_table_pos + i);
	}

	m_state = m_next_state = STATE_NORMAL1;
}

// Updates filter parameters for mode 2
void qsound_hle_device::state_refresh_filter_2()
{
	for (int ch = 0; ch < 2; ch++)
	{
		m_filter[ch].m_delay_pos = 0;
		m_filter[ch].m_tap_count = 45;

		for (int i = 0; i < 45; i++)
			m_filter[ch].m_taps[i] = (int16_t)read_dsp_rom(m_filter[ch].m_table_pos + i);

		m_alt_filter[ch].m_delay_pos = 0;
		m_alt_filter[ch].m_tap_count = 44;

		for (int i = 0; i < 44; i++)
			m_alt_filter[ch].m_taps[i] = (int16_t)read_dsp_rom(m_alt_filter[ch].m_table_pos + i);
	}

	m_state = m_next_state = STATE_NORMAL2;
}

// Updates a PCM voice. There are 16 voices, each are updated every sample
// with full rate and volume control.
int16_t qsound_hle_device::qsound_voice::update(qsound_hle_device &dsp, int32_t *echo_out)
{
	// Read sample from rom and apply volume
	const int16_t output = (m_volume * dsp.read_sample(m_bank, m_addr)) >> 14;

	*echo_out += (output * m_echo) << 2;

	// Add delta to the phase and loop back if required
	int32_t new_phase = m_rate + ((m_addr << 12) | (m_phase >> 4));

	if ((new_phase >> 12) >= m_end_addr)
		new_phase -= (m_loop_len << 12);

	new_phase = std::min<int32_t>(std::max<int32_t>(new_phase, -0x8000000), 0x7FFFFFF);
	m_addr = new_phase >> 12;
	m_phase = (new_phase << 4)&0xffff;

	return output;
}

// Updates an ADPCM voice. There are 3 voices, one is updated every sample
// (effectively making the ADPCM rate 1/3 of the master sample rate), and
// volume is set when starting samples only.
// The ADPCM algorithm is supposedly similar to Yamaha ADPCM. It also seems
// like Capcom never used it, so this was not emulated in the earlier QSound
// emulators.
int16_t qsound_hle_device::qsound_adpcm::update(qsound_hle_device &dsp, int16_t curr_sample, int nibble)
{
	int8_t step;
	if (!nibble)
	{
		// Mute voice when it reaches the end address.
		if (m_cur_addr == m_end_addr)
			m_cur_vol = 0;

		// Playback start flag
		if (m_flag)
		{
			curr_sample = 0;
			m_flag = 0;
			m_step_size = 10;
			m_cur_vol = m_volume;
			m_cur_addr = m_start_addr;
		}

		// get top nibble
		step = dsp.read_sample(m_bank, m_cur_addr) >> 8;
	}
	else
	{
		// get bottom nibble
		step = dsp.read_sample(m_bank, m_cur_addr++) >> 4;
	}

	// shift with sign extend
	step >>= 4;

	// delta = (0.5 + abs(step)) * m_step_size
	int32_t delta = ((1 + abs(step << 1)) * m_step_size) >> 1;
	if (step <= 0)
		delta = -delta;
	delta += curr_sample;
	delta = std::min<int32_t>(std::max<int32_t>(delta, -32768), 32767);

	m_step_size = (dsp.read_dsp_rom(DATA_ADPCM_TAB + 8 + step) * m_step_size) >> 6;
	m_step_size = std::min<int16_t>(std::max<int16_t>(m_step_size, 1), 2000);

	return (delta * m_cur_vol) >> 16;
}

// The echo effect is pretty simple. A moving average filter is used on
// the output from the delay line to smooth samples over time.
int16_t qsound_hle_device::qsound_echo::apply(int32_t input)
{
	// get average of last 2 samples from the delay line
	int32_t old_sample = m_delay_line[m_delay_pos];
	const int32_t last_sample = m_last_sample;
	m_last_sample = old_sample;
	old_sample = (old_sample + last_sample) >> 1;

	// add current sample to the delay line
	int32_t new_sample = input + ((old_sample * m_feedback) << 2);
	m_delay_line[m_delay_pos++] = new_sample >> 16;

	if (m_delay_pos >= m_length)
		m_delay_pos = 0;

	return old_sample;
}

// Process a sample update
void qsound_hle_device::state_normal_update()
{
	m_ready_flag = 0x80;

	// recalculate echo length
	if (m_state == STATE_NORMAL2)
		m_echo.m_length = m_echo.m_end_pos - DELAY_BASE_OFFSET2;
	else
		m_echo.m_length = m_echo.m_end_pos - DELAY_BASE_OFFSET;

	m_echo.m_length = std::min<int16_t>(std::max<int16_t>(m_echo.m_length, 0), 1024);

	// update PCM voices
	int32_t echo_input = 0;
	for (int i = 0; i < 16; i++)
		m_voice_output[i] = m_voice[i].update(*this, &echo_input);

	// update ADPCM voices (one every third sample)
	const int adpcm_voice = m_state_counter % 3;
	m_voice_output[16 + adpcm_voice] = m_adpcm[adpcm_voice].update(*this, m_voice_output[16 + adpcm_voice], m_state_counter / 3);

	int16_t echo_output = m_echo.apply(echo_input);

	// now, we do the magic stuff
	for (int ch = 0; ch < 2; ch++)
	{
		// Echo is output on the unfiltered component of the left channel and
		// the filtered component of the right channel.
		int32_t wet = (ch == 1) ? echo_output << 14 : 0;
		int32_t dry = (ch == 0) ? echo_output << 14 : 0;

		for (int i = 0; i < 19; i++)
		{
			uint16_t pan_index = m_voice_pan[i] + (ch * PAN_TABLE_CH_OFFSET);

			// Apply different volume tables on the dry and wet inputs.
			dry -= (m_voice_output[i] * (int16_t)read_dsp_rom(pan_index + PAN_TABLE_DRY));
			wet -= (m_voice_output[i] * (int16_t)read_dsp_rom(pan_index + PAN_TABLE_WET));
		}
		// Saturate accumulated voices
		dry = (std::min<int32_t>(std::max<int32_t>(dry, -0x1fffffff), 0x1fffffff)) << 2;
		wet = (std::min<int32_t>(std::max<int32_t>(wet, -0x1fffffff), 0x1fffffff)) << 2;

		// Apply FIR filter on 'wet' input
		wet = m_filter[ch].apply(wet >> 16);

		// in mode 2, we do this on the 'dry' input too
		if (m_state == STATE_NORMAL2)
			dry = m_alt_filter[ch].apply(dry >> 16);

		// output goes through a delay line and attenuation
		int32_t output = (m_wet[ch].apply(wet) + m_dry[ch].apply(dry));

		// DSP round function
		output = (output + 0x2000) & ~0x3fff;
		m_out[ch] = (std::min<int32_t>(std::max<int32_t>(output >> 14, -0x7fff), 0x7fff));

		if (m_delay_update)
		{
			m_wet[ch].update();
			m_dry[ch].update();
		}
	}

	m_delay_update = 0;

	// after 6 samples, the next state is executed.
	m_state_counter++;
	if (m_state_counter > 5)
	{
		m_state_counter = 0;
		m_state = m_next_state;
	}
}

// Apply the FIR filter used as the Q1 transfer function
int32_t qsound_hle_device::qsound_fir::apply(int16_t input)
{
	int32_t output = 0, tap = 0;
	for (; tap < (m_tap_count - 1); tap++)
	{
		output -= (m_taps[tap] * m_delay_line[m_delay_pos++]) << 2;

		if (m_delay_pos >= m_tap_count - 1)
			m_delay_pos = 0;
	}

	output -= (m_taps[tap] * input) << 2;

	m_delay_line[m_delay_pos++] = input;
	if (m_delay_pos >= m_tap_count - 1)
		m_delay_pos = 0;

	return output;
}

// Apply delay line and component volume
int32_t qsound_hle_device::qsound_delay::apply(const int32_t input)
{
	m_delay_line[m_write_pos++] = input >> 16;
	if (m_write_pos >= 51)
		m_write_pos = 0;

	const int32_t output = m_delay_line[m_read_pos++] * m_volume;
	if (m_read_pos >= 51)
		m_read_pos = 0;

	return output;
}

// Update the delay read position to match new delay length
void qsound_hle_device::qsound_delay::update()
{
	const int16_t new_read_pos = (m_write_pos - m_delay) % 51;
	if (new_read_pos < 0)
		m_read_pos = new_read_pos + 51;
	else
		m_read_pos = new_read_pos;
}